i18n: add Korean/English localization via strings.xml
Replace hardcoded Korean UI strings in Piezo, CatheterizeSheet, VoidingDiary, and UrineCameraScreen with stringResource() calls. Add values/strings.xml (English) and values-ko/strings.xml (Korean). System language setting now controls app language. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+18
-16
@@ -14,12 +14,14 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.example.medilightv2android.R
|
||||
import com.example.medilightv2android.measure.VoidingRecord
|
||||
import com.example.medilightv2android.measure.VoidingRecordStore
|
||||
import com.example.medilightv2android.speech.SpeechRecognizerManager
|
||||
@@ -56,13 +58,13 @@ fun CatheterizeSheet(
|
||||
VoidingRecordStore.add(context, VoidingRecord(volume = volume, method = method))
|
||||
onCatheterized()
|
||||
isSaved = true
|
||||
Toast.makeText(context, "배뇨일지에 등록되었습니다", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context.getString(R.string.registered_diary), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun startVoice() {
|
||||
if (!hasAudioPerm) { audioPermLauncher.launch(android.Manifest.permission.RECORD_AUDIO); return }
|
||||
speechManager?.destroy()
|
||||
isListening = true; voiceStatus = "듣는 중..."
|
||||
isListening = true; voiceStatus = context.getString(R.string.listening)
|
||||
speechManager = SpeechRecognizerManager(
|
||||
context = context,
|
||||
onResult = { candidates ->
|
||||
@@ -85,14 +87,14 @@ fun CatheterizeSheet(
|
||||
volumeText = it.volumeMl.toString()
|
||||
voiceStatus = "\"${it.rawText}\" → ${it.volumeMl} ml"
|
||||
saveRecord(it.volumeMl, "voice")
|
||||
} ?: run { voiceStatus = "인식 실패: ${candidates.firstOrNull() ?: ""}" }
|
||||
} ?: run { voiceStatus = context.getString(R.string.recognition_fail, candidates.firstOrNull() ?: "") }
|
||||
},
|
||||
onError = { code -> isListening = false; voiceStatus = when(code) {
|
||||
android.speech.SpeechRecognizer.ERROR_NO_MATCH -> "인식된 음성 없음"
|
||||
android.speech.SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> "시간 초과"
|
||||
else -> "오류 ($code)"
|
||||
android.speech.SpeechRecognizer.ERROR_NO_MATCH -> context.getString(R.string.recognition_fail, "")
|
||||
android.speech.SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> context.getString(R.string.error_code, code)
|
||||
else -> context.getString(R.string.error_code, code)
|
||||
}},
|
||||
onReady = { voiceStatus = "말씀하세요..." }
|
||||
onReady = { voiceStatus = context.getString(R.string.speak_now) }
|
||||
)
|
||||
speechManager?.startListening()
|
||||
}
|
||||
@@ -106,7 +108,7 @@ fun CatheterizeSheet(
|
||||
// 저장 완료 화면
|
||||
Icon(Icons.Default.CheckCircle, null, Modifier.size(64.dp), tint = Color(0xFF4CAF50))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text("배뇨일지에 등록되었습니다", fontSize = 18.sp, fontWeight = FontWeight.SemiBold)
|
||||
Text(stringResource(R.string.registered_diary), fontSize = 18.sp, fontWeight = FontWeight.SemiBold)
|
||||
if (volumeText.isNotEmpty()) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("$volumeText ml", fontSize = 32.sp, fontWeight = FontWeight.Bold, color = Color(0xFF007AFF))
|
||||
@@ -116,10 +118,10 @@ fun CatheterizeSheet(
|
||||
onClick = onDismiss,
|
||||
Modifier.fillMaxWidth().height(48.dp),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) { Text("확인") }
|
||||
) { Text(stringResource(R.string.confirm)) }
|
||||
} else {
|
||||
// 입력 모드 선택
|
||||
Text("배뇨 기록", fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.voiding_record), fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
// 3 탭 버튼
|
||||
@@ -135,7 +137,7 @@ fun CatheterizeSheet(
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(Icons.Default.Edit, null, Modifier.size(20.dp), tint = if (selectedTab == 0) Color.White else Color(0xFFFF9800))
|
||||
Text("수기", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = if (selectedTab == 0) Color.White else Color(0xFFFF9800))
|
||||
Text(stringResource(R.string.manual_input), fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = if (selectedTab == 0) Color.White else Color(0xFFFF9800))
|
||||
}
|
||||
}
|
||||
// 음성
|
||||
@@ -149,7 +151,7 @@ fun CatheterizeSheet(
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(Icons.Default.SettingsVoice, null, Modifier.size(20.dp), tint = if (selectedTab == 1) Color.White else Color(0xFF007AFF))
|
||||
Text("음성", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = if (selectedTab == 1) Color.White else Color(0xFF007AFF))
|
||||
Text(stringResource(R.string.voice_input), fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = if (selectedTab == 1) Color.White else Color(0xFF007AFF))
|
||||
}
|
||||
}
|
||||
// 카메라
|
||||
@@ -161,7 +163,7 @@ fun CatheterizeSheet(
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(Icons.Default.CameraAlt, null, Modifier.size(20.dp), tint = Color(0xFF4CAF50))
|
||||
Text("카메라", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = Color(0xFF4CAF50))
|
||||
Text(stringResource(R.string.camera_input), fontSize = 11.sp, fontWeight = FontWeight.SemiBold, color = Color(0xFF4CAF50))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +177,7 @@ fun CatheterizeSheet(
|
||||
OutlinedTextField(
|
||||
value = volumeText,
|
||||
onValueChange = { volumeText = it.filter { c -> c.isDigit() }.take(4) },
|
||||
placeholder = { Text("예: 200", color = Color.Gray) },
|
||||
placeholder = { Text(stringResource(R.string.volume_placeholder), color = Color.Gray) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
singleLine = true,
|
||||
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 32.sp, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center),
|
||||
@@ -193,7 +195,7 @@ fun CatheterizeSheet(
|
||||
modifier = Modifier.fillMaxWidth().height(48.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF9800))
|
||||
) { Text("저장하기", fontWeight = FontWeight.SemiBold) }
|
||||
) { Text(stringResource(R.string.save_to_diary), fontWeight = FontWeight.SemiBold) }
|
||||
}
|
||||
1 -> {
|
||||
// 음성 입력
|
||||
@@ -209,7 +211,7 @@ fun CatheterizeSheet(
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Text(
|
||||
if (isListening) "듣는 중..." else "탭하여 음성 입력",
|
||||
if (isListening) stringResource(R.string.listening) else stringResource(R.string.tap_to_speak),
|
||||
color = Color.Gray, fontSize = 14.sp
|
||||
)
|
||||
if (voiceStatus.isNotEmpty()) {
|
||||
|
||||
+22
-20
@@ -35,7 +35,9 @@ import com.example.medilightv2android.ui.components.BladdyRiveView
|
||||
import com.example.medilightv2android.ui.components.BleDisconnectionBanner
|
||||
import com.example.medilightv2android.ui.components.BleReconnectionState
|
||||
import com.example.medilightv2android.ui.components.DonutChartView
|
||||
import com.example.medilightv2android.R
|
||||
import com.example.medilightv2android.ui.theme.*
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun PiezoMonitoringView(appState: AppState) {
|
||||
@@ -139,23 +141,23 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
if (showCatheterizeSheet) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showCatheterizeSheet = false },
|
||||
title = { Text("배뇨 기록", fontWeight = FontWeight.Bold) },
|
||||
text = { Text("입력 방식을 선택하세요") },
|
||||
title = { Text(stringResource(R.string.voiding_record), fontWeight = FontWeight.Bold) },
|
||||
text = { Text(stringResource(R.string.select_input_method)) },
|
||||
confirmButton = {},
|
||||
dismissButton = {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
TextButton(onClick = {
|
||||
showCatheterizeSheet = false
|
||||
showManualInput = true
|
||||
}) { Text("수기") }
|
||||
}) { Text(stringResource(R.string.manual_input)) }
|
||||
TextButton(onClick = {
|
||||
showCatheterizeSheet = false
|
||||
showVoiceInput = true
|
||||
}) { Text("음성") }
|
||||
}) { Text(stringResource(R.string.voice_input)) }
|
||||
TextButton(onClick = {
|
||||
showCatheterizeSheet = false
|
||||
showUrineCamera = true
|
||||
}) { Text("카메라") }
|
||||
}) { Text(stringResource(R.string.camera_input)) }
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -166,13 +168,13 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
var volumeText by remember { mutableStateOf(appState.estimatedVolume.toString()) }
|
||||
AlertDialog(
|
||||
onDismissRequest = { showManualInput = false },
|
||||
title = { Text("소변량 입력") },
|
||||
title = { Text(stringResource(R.string.volume_input_title)) },
|
||||
text = {
|
||||
androidx.compose.foundation.layout.Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
OutlinedTextField(
|
||||
value = volumeText,
|
||||
onValueChange = { volumeText = it.filter { c -> c.isDigit() }.take(4) },
|
||||
placeholder = { Text("예: ${appState.estimatedVolume}") },
|
||||
placeholder = { Text(stringResource(R.string.volume_placeholder)) },
|
||||
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(keyboardType = androidx.compose.ui.text.input.KeyboardType.Number),
|
||||
singleLine = true,
|
||||
modifier = Modifier.weight(1f)
|
||||
@@ -189,12 +191,12 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
context, com.example.medilightv2android.measure.VoidingRecord(volume = vol, method = "manual")
|
||||
)
|
||||
appState.resetLevel()
|
||||
android.widget.Toast.makeText(context, "배뇨일지에 등록되었습니다", android.widget.Toast.LENGTH_SHORT).show()
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.registered_diary), android.widget.Toast.LENGTH_SHORT).show()
|
||||
showManualInput = false
|
||||
}
|
||||
}) { Text("저장") }
|
||||
}) { Text(stringResource(R.string.save)) }
|
||||
},
|
||||
dismissButton = { TextButton(onClick = { showManualInput = false }) { Text("취소") } }
|
||||
dismissButton = { TextButton(onClick = { showManualInput = false }) { Text(stringResource(R.string.cancel)) } }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -221,14 +223,14 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
context, com.example.medilightv2android.measure.VoidingRecord(volume = parsed.volumeMl, method = "voice")
|
||||
)
|
||||
appState.resetLevel()
|
||||
android.widget.Toast.makeText(context, "${parsed.volumeMl} ml 등록되었습니다", android.widget.Toast.LENGTH_SHORT).show()
|
||||
android.widget.Toast.makeText(context, context.getString(R.string.registered_ml, parsed.volumeMl), android.widget.Toast.LENGTH_SHORT).show()
|
||||
showVoiceInput = false
|
||||
} else {
|
||||
voiceResult = "인식 실패: ${candidates.firstOrNull() ?: ""}"
|
||||
voiceResult = context.getString(R.string.recognition_fail, candidates.firstOrNull() ?: "")
|
||||
}
|
||||
},
|
||||
onError = { code -> isListening = false; voiceResult = "오류 ($code)" },
|
||||
onReady = { voiceResult = "말씀하세요..." }
|
||||
onError = { code -> isListening = false; voiceResult = context.getString(R.string.error_code, code) },
|
||||
onReady = { voiceResult = context.getString(R.string.speak_now) }
|
||||
)
|
||||
speechMgr?.startListening()
|
||||
}
|
||||
@@ -236,17 +238,17 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { speechMgr?.destroy(); showVoiceInput = false },
|
||||
title = { Text(if (isListening) "듣는 중..." else "음성 입력") },
|
||||
text = { Text(voiceResult ?: "마이크 권한을 확인하세요") },
|
||||
title = { Text(stringResource(if (isListening) R.string.listening else R.string.voice_input)) },
|
||||
text = { Text(voiceResult ?: stringResource(R.string.check_mic_permission)) },
|
||||
confirmButton = {
|
||||
if (!isListening && voiceResult != null) {
|
||||
TextButton(onClick = {
|
||||
isListening = true; voiceResult = null
|
||||
speechMgr?.startListening()
|
||||
}) { Text("다시 시도") }
|
||||
}) { Text(stringResource(R.string.retry)) }
|
||||
}
|
||||
},
|
||||
dismissButton = { TextButton(onClick = { speechMgr?.destroy(); showVoiceInput = false }) { Text("취소") } }
|
||||
dismissButton = { TextButton(onClick = { speechMgr?.destroy(); showVoiceInput = false }) { Text(stringResource(R.string.cancel)) } }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -275,10 +277,10 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
Icon(Icons.Default.Home, "Home", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
}
|
||||
IconButton(onClick = { appState.currentScreen = AppScreen.VOIDING_DIARY }, modifier = Modifier.size(48.dp)) {
|
||||
Icon(Icons.Default.Description, contentDescription = "배뇨일지", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
Icon(Icons.Default.Description, stringResource(R.string.voiding_diary), tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
}
|
||||
IconButton(onClick = { appState.currentScreen = AppScreen.REMINDER_SETTINGS }, modifier = Modifier.size(48.dp)) {
|
||||
Icon(Icons.Default.Notifications, contentDescription = "배뇨 알리미", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
Icon(Icons.Default.Notifications, contentDescription = "Reminder", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
}
|
||||
if (!isCompact) {
|
||||
Text("Bladder Monitor", fontSize = 20.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp))
|
||||
|
||||
+21
-19
@@ -32,8 +32,10 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.example.medilightv2android.R
|
||||
import com.example.medilightv2android.measure.SimpleMeasureService
|
||||
import com.example.medilightv2android.measure.VoidingRecord
|
||||
import com.example.medilightv2android.measure.VoidingRecordStore
|
||||
@@ -149,9 +151,9 @@ fun UrineCameraScreen(
|
||||
if (!hasCameraPerm) {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text("카메라 권한이 필요합니다")
|
||||
Text(stringResource(R.string.camera_permission_needed))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Button(onClick = { permLauncher.launch(Manifest.permission.CAMERA) }) { Text("권한 허용") }
|
||||
Button(onClick = { permLauncher.launch(Manifest.permission.CAMERA) }) { Text(stringResource(R.string.grant_permission)) }
|
||||
}
|
||||
}
|
||||
} else if (capturedBitmap != null) {
|
||||
@@ -161,8 +163,8 @@ fun UrineCameraScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
||||
IconButton(onClick = onCancel) { Icon(Icons.Default.ArrowBack, "뒤로") }
|
||||
Text("소변량 측정", fontWeight = FontWeight.SemiBold)
|
||||
IconButton(onClick = onCancel) { Icon(Icons.Default.ArrowBack, stringResource(R.string.back)) }
|
||||
Text(stringResource(R.string.urine_measurement), fontWeight = FontWeight.SemiBold)
|
||||
Spacer(Modifier.size(48.dp))
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
@@ -173,37 +175,37 @@ fun UrineCameraScreen(
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
CircularProgressIndicator(Modifier.size(24.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("분석 중...", color = Color.Gray)
|
||||
Text(stringResource(R.string.analyzing), color = Color.Gray)
|
||||
}
|
||||
} else if (analysisComplete) {
|
||||
if (resultVolume != null && resultVolume!! > 0) {
|
||||
Text("측정 결과", color = Color.Gray, fontSize = 14.sp)
|
||||
Text(stringResource(R.string.measurement_result), color = Color.Gray, fontSize = 14.sp)
|
||||
Row(verticalAlignment = Alignment.Bottom) {
|
||||
Text("$resultVolume", fontSize = 48.sp, fontWeight = FontWeight.Bold, color = Color(0xFF007AFF))
|
||||
Text(" ml", fontSize = 20.sp, color = Color.Gray, modifier = Modifier.padding(bottom = 8.dp))
|
||||
}
|
||||
} else {
|
||||
Text("측정 실패", fontSize = 20.sp, color = Color.Red)
|
||||
Text("밝은 곳에서 다시 촬영해주세요", color = Color.Gray, fontSize = 14.sp)
|
||||
Text(stringResource(R.string.measurement_fail), fontSize = 20.sp, color = Color.Red)
|
||||
Text(stringResource(R.string.retry_in_bright), color = Color.Gray, fontSize = 14.sp)
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
OutlinedButton(
|
||||
onClick = { capturedBitmap = null; originalBitmap = null; analysisComplete = false; resultVolume = null; isAutoCapturing = false; detectionStartTime = null; autoCaptureProgress = 0f },
|
||||
Modifier.weight(1f).height(48.dp), shape = RoundedCornerShape(12.dp)
|
||||
) { Text("다시 촬영") }
|
||||
) { Text(stringResource(R.string.retake_photo)) }
|
||||
Button(
|
||||
onClick = {
|
||||
val vol = resultVolume
|
||||
if (vol != null && vol > 0) {
|
||||
VoidingRecordStore.add(context, VoidingRecord(volume = vol, method = "camera"))
|
||||
Toast.makeText(context, "배뇨일지에 등록되었습니다", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context.getString(R.string.registered_diary), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
onDone(vol)
|
||||
},
|
||||
Modifier.weight(1f).height(48.dp), shape = RoundedCornerShape(12.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4CAF50))
|
||||
) { Text("저장하기", fontWeight = FontWeight.SemiBold) }
|
||||
) { Text(stringResource(R.string.save_to_diary), fontWeight = FontWeight.SemiBold) }
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
@@ -242,14 +244,14 @@ fun UrineCameraScreen(
|
||||
|
||||
// 상단
|
||||
Row(Modifier.fillMaxWidth().statusBarsPadding().padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
IconButton(onClick = onCancel) { Icon(Icons.Default.Close, "닫기", tint = Color.White) }
|
||||
IconButton(onClick = onCancel) { Icon(Icons.Default.Close, stringResource(R.string.close), tint = Color.White) }
|
||||
Spacer(Modifier.weight(1f))
|
||||
val statusText = when {
|
||||
!isModelLoaded -> "모델 로딩 중..."
|
||||
isAutoCapturing -> "자동 촬영 중..."
|
||||
autoCaptureProgress > 0 -> "유지하세요... ${(autoCaptureProgress * 100).toInt()}%"
|
||||
currentDetection != null -> "소변컵 감지됨"
|
||||
else -> "소변컵을 찾는 중..."
|
||||
!isModelLoaded -> stringResource(R.string.model_loading)
|
||||
isAutoCapturing -> stringResource(R.string.auto_capturing)
|
||||
autoCaptureProgress > 0 -> stringResource(R.string.hold_still, (autoCaptureProgress * 100).toInt())
|
||||
currentDetection != null -> stringResource(R.string.cup_detected)
|
||||
else -> stringResource(R.string.searching_cup)
|
||||
}
|
||||
Text(statusText, color = Color.White, fontSize = 12.sp, fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.background(Color.Black.copy(alpha = 0.6f), RoundedCornerShape(20.dp)).padding(horizontal = 12.dp, vertical = 6.dp))
|
||||
@@ -258,8 +260,8 @@ fun UrineCameraScreen(
|
||||
|
||||
// 가이드
|
||||
Column(Modifier.align(Alignment.Center).offset(y = 120.dp).background(Color.Black.copy(alpha = 0.5f), RoundedCornerShape(10.dp)).padding(horizontal = 16.dp, vertical = 8.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text("눈금이 보이는 면을 정면으로", color = Color.White, fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
Text("밝은 곳에서 촬영해주세요", color = Color(0xFFFFD54F), fontSize = 12.sp)
|
||||
Text(stringResource(R.string.camera_guide_1), color = Color.White, fontSize = 13.sp, fontWeight = FontWeight.SemiBold)
|
||||
Text(stringResource(R.string.camera_guide_2), color = Color(0xFFFFD54F), fontSize = 12.sp)
|
||||
if (autoCaptureProgress > 0) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
LinearProgressIndicator(progress = { autoCaptureProgress }, Modifier.fillMaxWidth(0.6f).height(4.dp).clip(RoundedCornerShape(2.dp)), color = Color.Green, trackColor = Color.White.copy(alpha = 0.3f))
|
||||
|
||||
+17
-17
@@ -15,10 +15,12 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.medilightv2android.R
|
||||
import com.example.medilightv2android.measure.VoidingRecord
|
||||
import com.example.medilightv2android.measure.VoidingRecordStore
|
||||
import com.example.medilightv2android.ui.theme.MlCardBackground
|
||||
@@ -38,8 +40,6 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
||||
var showDeleteConfirm by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) }
|
||||
val displayDateFormat = remember { SimpleDateFormat("M월 d일 (E)", Locale.KOREA) }
|
||||
|
||||
val grouped = remember(records) {
|
||||
records.groupBy { dateFormat.format(Date(it.timestamp)) }
|
||||
.toSortedMap(compareByDescending { it })
|
||||
@@ -51,9 +51,9 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("배뇨일지", fontWeight = FontWeight.SemiBold) },
|
||||
title = { Text(stringResource(R.string.voiding_diary), fontWeight = FontWeight.SemiBold) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "뒤로") }
|
||||
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(Icons.Default.Description, contentDescription = null, Modifier.size(48.dp), Color.Gray.copy(alpha = 0.5f))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("기록이 없습니다", color = Color.Gray)
|
||||
Text(stringResource(R.string.no_records), color = Color.Gray)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -79,7 +79,7 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
||||
item {
|
||||
val displayDate = try {
|
||||
val d = dateFormat.parse(date)
|
||||
displayDateFormat.format(d!!)
|
||||
SimpleDateFormat("M/d (E)", Locale.getDefault()).format(d!!)
|
||||
} catch (_: Exception) { date }
|
||||
|
||||
Text(displayDate, fontWeight = FontWeight.Bold, fontSize = 15.sp,
|
||||
@@ -106,21 +106,21 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
||||
record.activity == "awake" -> {
|
||||
Icon(Icons.Default.WbSunny, contentDescription = null, Modifier.size(16.dp), Color(0xFFFF9800))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("기상")
|
||||
Text(stringResource(R.string.awake))
|
||||
}
|
||||
record.activity == "sleep" -> {
|
||||
Icon(Icons.Default.DarkMode, contentDescription = null, Modifier.size(16.dp), Color(0xFF9C27B0))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("취침")
|
||||
Text(stringResource(R.string.sleep))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
val methodLabel = when (record.method) {
|
||||
"voice" -> "음성"
|
||||
"camera" -> "카메라"
|
||||
else -> "수기"
|
||||
"voice" -> stringResource(R.string.method_voice)
|
||||
"camera" -> stringResource(R.string.method_camera)
|
||||
else -> stringResource(R.string.method_manual)
|
||||
}
|
||||
Text(methodLabel, fontSize = 10.sp, color = Color.Gray,
|
||||
modifier = Modifier.background(Color.Gray.copy(alpha = 0.15f), RoundedCornerShape(4.dp))
|
||||
@@ -132,7 +132,7 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
||||
onClick = { showDeleteConfirm = record.id },
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Delete, contentDescription = "삭제", Modifier.size(20.dp), Color.Gray)
|
||||
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.delete), Modifier.size(20.dp), Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text("총 ${records.size}건", color = Color.Gray, fontSize = 12.sp,
|
||||
Text(stringResource(R.string.total_records, records.size), color = Color.Gray, fontSize = 12.sp,
|
||||
modifier = Modifier.fillMaxWidth(), textAlign = androidx.compose.ui.text.style.TextAlign.Center)
|
||||
}
|
||||
}
|
||||
@@ -151,16 +151,16 @@ fun VoidingDiaryView(onBack: () -> Unit) {
|
||||
showDeleteConfirm?.let { id ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteConfirm = null },
|
||||
title = { Text("기록 삭제") },
|
||||
text = { Text("이 기록을 삭제하시겠습니까?") },
|
||||
title = { Text(stringResource(R.string.delete_record)) },
|
||||
text = { Text(stringResource(R.string.delete_confirm_msg)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
VoidingRecordStore.delete(context, id)
|
||||
records = VoidingRecordStore.getAll(context).sortedByDescending { it.timestamp }
|
||||
showDeleteConfirm = null
|
||||
}) { Text("삭제", color = Color.Red) }
|
||||
}) { Text(stringResource(R.string.delete), color = Color.Red) }
|
||||
},
|
||||
dismissButton = { TextButton(onClick = { showDeleteConfirm = null }) { Text("취소") } }
|
||||
dismissButton = { TextButton(onClick = { showDeleteConfirm = null }) { Text(stringResource(R.string.cancel)) } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user