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
|
||||
|
||||
// Material theme colors
|
||||
// Material theme colors (legacy — Material3 template default. 실사용 X, LightColorScheme 를 MlPrimary 로 override.)
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
@@ -10,7 +10,7 @@ val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
|
||||
// MediLight theme colors
|
||||
// ─────────── Brand colors ───────────
|
||||
val MlPrimary = Color(0xFF008FFF)
|
||||
val MlTeal = Color(0xFF00DB9F)
|
||||
val MlBackground = Color(0xFFF5F8FF)
|
||||
@@ -18,3 +18,27 @@ val MlCardBackground = Color.White
|
||||
val MlSecondaryText = Color(0xFF80808C)
|
||||
val MlInputBackground = Color(0xFFF5F8FF)
|
||||
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.lightColorScheme
|
||||
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(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
primary = MlPrimary,
|
||||
onPrimary = Color.White,
|
||||
secondary = MlTeal,
|
||||
onSecondary = Color.White,
|
||||
tertiary = MlAccentPurple,
|
||||
onTertiary = Color.White,
|
||||
background = MlBackground,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
surface = MlCardBackground,
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
error = MlCritical,
|
||||
onError = Color.White,
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -144,25 +144,28 @@ fun ClinicalHomeView(appState: AppState) {
|
||||
|
||||
// 2026-07-09: 자동 업로드 진행/결과 배너.
|
||||
if (autoUploadPending > 0 || autoUploadingName != null) {
|
||||
// 2026-07-10: semantic token 사용 (MlInfo = 진행 중 파랑).
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.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),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp),
|
||||
strokeWidth = 2.dp,
|
||||
color = Color(0xFF1976D2)
|
||||
color = com.medithings.vesiscan.ui.theme.MlInfo
|
||||
)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Column {
|
||||
Text("자동 업로드 진행 중 · 남은 세션 $autoUploadPending",
|
||||
fontSize = 13.sp, fontWeight = FontWeight.SemiBold,
|
||||
color = Color(0xFF1976D2))
|
||||
color = com.medithings.vesiscan.ui.theme.MlInfo)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -171,7 +174,9 @@ fun ClinicalHomeView(appState: AppState) {
|
||||
} else if (autoUploadResult != null) {
|
||||
val msg = autoUploadResult ?: ""
|
||||
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(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
|
||||
+21
-85
@@ -156,99 +156,35 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
// 직전 walking 상태 추적 — false → true 전이 시 critical 알람
|
||||
var prevIsWalking by remember { mutableStateOf(false) }
|
||||
// Wakeup alarm (warning, 주황) — LYING → NON_LYING/WALKING 전이
|
||||
// 2026-07-10: 3개 알람 Dialog 를 AlertModalDialog 로 통합 (ui/components/AlertModalDialog.kt).
|
||||
if (showWakeupAlarm) {
|
||||
androidx.compose.ui.window.Dialog(
|
||||
onDismissRequest = { },
|
||||
properties = androidx.compose.ui.window.DialogProperties(
|
||||
dismissOnBackPress = false, dismissOnClickOutside = false
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.92f)
|
||||
.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.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
com.medithings.vesiscan.ui.components.AlertModalDialog(
|
||||
backgroundColor = com.medithings.vesiscan.ui.theme.MlWarning,
|
||||
icon = Icons.Default.Warning,
|
||||
title = stringResource(R.string.alarm_patient_woke_up),
|
||||
subtitle = stringResource(R.string.alarm_patient_woke_up_en),
|
||||
detail = stringResource(R.string.alarm_lying_duration, mins, secs),
|
||||
confirmLabel = stringResource(R.string.confirm),
|
||||
onConfirm = { showWakeupAlarm = false },
|
||||
)
|
||||
}
|
||||
|
||||
// Walking alarm (critical, 빨강) — NON_LYING/LYING → WALKING 전이 (보행 시작)
|
||||
if (showWalkingAlarm) {
|
||||
androidx.compose.ui.window.Dialog(
|
||||
onDismissRequest = { },
|
||||
properties = androidx.compose.ui.window.DialogProperties(
|
||||
dismissOnBackPress = false, dismissOnClickOutside = false
|
||||
com.medithings.vesiscan.ui.components.AlertModalDialog(
|
||||
backgroundColor = com.medithings.vesiscan.ui.theme.MlCritical,
|
||||
icon = Icons.Default.DirectionsWalk,
|
||||
title = stringResource(R.string.alarm_patient_walking),
|
||||
subtitle = stringResource(R.string.alarm_patient_walking_en),
|
||||
detail = stringResource(R.string.alarm_immediate_check),
|
||||
confirmLabel = stringResource(R.string.confirm),
|
||||
onConfirm = { showWalkingAlarm = false },
|
||||
widthFraction = 0.94f,
|
||||
iconSize = 64.dp,
|
||||
titleSize = 30.sp,
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.94f)
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(Color(0xFFD32F2F))
|
||||
.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
|
||||
|
||||
+10
-42
@@ -208,50 +208,18 @@ fun PlacementGuideView(appState: AppState) {
|
||||
)
|
||||
|
||||
// 센서 분리 알림 (주황 warning) — wakeup 알람과 동일 패턴
|
||||
// 2026-07-10: AlertModalDialog 로 통합 (ui/components/AlertModalDialog.kt).
|
||||
if (showDetachAlert) {
|
||||
androidx.compose.ui.window.Dialog(
|
||||
onDismissRequest = { },
|
||||
properties = androidx.compose.ui.window.DialogProperties(
|
||||
dismissOnBackPress = false, dismissOnClickOutside = false
|
||||
com.medithings.vesiscan.ui.components.AlertModalDialog(
|
||||
backgroundColor = com.medithings.vesiscan.ui.theme.MlWarning,
|
||||
icon = Icons.Default.Warning,
|
||||
title = stringResource(R.string.sensor_detached_title),
|
||||
subtitle = stringResource(R.string.sensor_detached_subtitle),
|
||||
detail = stringResource(R.string.sensor_detached_desc),
|
||||
confirmLabel = stringResource(R.string.confirm),
|
||||
onConfirm = { showDetachAlert = false },
|
||||
titleSize = 26.sp,
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.92f)
|
||||
.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(
|
||||
|
||||
Reference in New Issue
Block a user