feat(locale): Phase 2 — 모든 사용자 노출 Korean 문자열 → strings.xml 추출 + 영문 번역
Sub-agent 작업 결과:
- values/strings.xml + values-ko/strings.xml 각각 71 → 194 줄 (약 85 신규 string)
- 17 파일 수정 (UI views 9개, components 3개, managers 1개, services 2개)
상세:
- ui/views/{home, consent, registration, reminder, monitoring (3), personalization, connection}
- components/{BatteryWarningBanner, FirmwareWarningBanner, LockableSlider}
- managers/AlignmentAdvisorV2 — Out/AdvisorState 에 msgKey + msgArgs 추가 (UI 가 context.getString 으로 localize)
- services/{ReminderService, BleForegroundService}
의도적으로 skip:
- ClinicalHomeView / ClinicalLiveView (R&D-only dev mode)
- PiezoMonitoring dev panel labels (dev 도구)
- AlignmentAdvice / 2-phase AlignGuide (UI 미사용)
- 이미 영어인 파일들 (Onboarding, Pin, SensorSelect, Calibration 등)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.example.medilightv2android.managers
|
package com.example.medilightv2android.managers
|
||||||
|
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.walldetect.MethodDResult
|
import com.example.medilightv2android.walldetect.MethodDResult
|
||||||
import com.example.medilightv2android.walldetect.MethodDRunner
|
import com.example.medilightv2android.walldetect.MethodDRunner
|
||||||
import com.example.medilightv2android.walldetect.algo.methodd.MethodDParams
|
import com.example.medilightv2android.walldetect.algo.methodd.MethodDParams
|
||||||
@@ -32,6 +33,10 @@ data class AdvisorState(
|
|||||||
val state: String, // 'accum' 또는 'commit'
|
val state: String, // 'accum' 또는 'commit'
|
||||||
val action: AlignAction?,
|
val action: AlignAction?,
|
||||||
val msg: String,
|
val msg: String,
|
||||||
|
/** Localized message string resource id (null if no localized message). */
|
||||||
|
val msgKey: Int? = null,
|
||||||
|
/** Args for msgKey (formatted into string). */
|
||||||
|
val msgArgs: List<Any> = emptyList(),
|
||||||
val dets: List<MethodDResult?>?,
|
val dets: List<MethodDResult?>?,
|
||||||
val summary: AlignSummary?,
|
val summary: AlignSummary?,
|
||||||
val accumProgress: Int = 0, // 'accum' 일 때 (현재/총)
|
val accumProgress: Int = 0, // 'accum' 일 때 (현재/총)
|
||||||
@@ -161,7 +166,16 @@ class AlignGuide4Stage(
|
|||||||
) {
|
) {
|
||||||
enum class Phase4 { INITIAL_ACCUM, VERTICAL_CLIMB, CH3_STABILIZE, LR_BALANCE }
|
enum class Phase4 { INITIAL_ACCUM, VERTICAL_CLIMB, CH3_STABILIZE, LR_BALANCE }
|
||||||
|
|
||||||
data class Out(val action: AlignAction?, val msg: String, val phase: Phase4, val progress: Int = 0)
|
data class Out(
|
||||||
|
val action: AlignAction?,
|
||||||
|
val msg: String,
|
||||||
|
val phase: Phase4,
|
||||||
|
val progress: Int = 0,
|
||||||
|
/** Localized string resource id for user-facing UI (msg is dev/debug). */
|
||||||
|
val msgKey: Int? = null,
|
||||||
|
/** Args for msgKey formatting. */
|
||||||
|
val msgArgs: List<Any> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
var phase: Phase4 = Phase4.INITIAL_ACCUM
|
var phase: Phase4 = Phase4.INITIAL_ACCUM
|
||||||
private set
|
private set
|
||||||
@@ -191,7 +205,9 @@ class AlignGuide4Stage(
|
|||||||
initialCount++
|
initialCount++
|
||||||
if (initialCount < initialTarget) {
|
if (initialCount < initialTarget) {
|
||||||
return Out(null, "잠시 그대로 두세요 (${initialCount}/${initialTarget})",
|
return Out(null, "잠시 그대로 두세요 (${initialCount}/${initialTarget})",
|
||||||
phase, initialCount)
|
phase, initialCount,
|
||||||
|
msgKey = R.string.align_hold_still,
|
||||||
|
msgArgs = listOf(initialCount, initialTarget))
|
||||||
}
|
}
|
||||||
phase = Phase4.VERTICAL_CLIMB
|
phase = Phase4.VERTICAL_CLIMB
|
||||||
}
|
}
|
||||||
@@ -200,7 +216,8 @@ class AlignGuide4Stage(
|
|||||||
if (phase == Phase4.VERTICAL_CLIMB) {
|
if (phase == Phase4.VERTICAL_CLIMB) {
|
||||||
if (!s.ch3) {
|
if (!s.ch3) {
|
||||||
return Out(AlignAction.MOVE_UP, "↑ 위로 조금씩 올리세요",
|
return Out(AlignAction.MOVE_UP, "↑ 위로 조금씩 올리세요",
|
||||||
phase, 0)
|
phase, 0,
|
||||||
|
msgKey = R.string.align_move_up)
|
||||||
}
|
}
|
||||||
phase = Phase4.CH3_STABILIZE
|
phase = Phase4.CH3_STABILIZE
|
||||||
stabilizeCount = 0
|
stabilizeCount = 0
|
||||||
@@ -216,12 +233,15 @@ class AlignGuide4Stage(
|
|||||||
phase = Phase4.VERTICAL_CLIMB
|
phase = Phase4.VERTICAL_CLIMB
|
||||||
stabilizeCount = 0
|
stabilizeCount = 0
|
||||||
return Out(AlignAction.MOVE_UP, "↑ 살짝 놓침 — 다시 위로",
|
return Out(AlignAction.MOVE_UP, "↑ 살짝 놓침 — 다시 위로",
|
||||||
phase, 0)
|
phase, 0,
|
||||||
|
msgKey = R.string.align_recover_up)
|
||||||
}
|
}
|
||||||
if (stabilizeCount < stabilizeTarget) {
|
if (stabilizeCount < stabilizeTarget) {
|
||||||
return Out(AlignAction.STOP,
|
return Out(AlignAction.STOP,
|
||||||
"✓ 좋아요! 그 위치 유지 (${stabilizeCount}/${stabilizeTarget})",
|
"✓ 좋아요! 그 위치 유지 (${stabilizeCount}/${stabilizeTarget})",
|
||||||
phase, stabilizeCount)
|
phase, stabilizeCount,
|
||||||
|
msgKey = R.string.align_hold_position,
|
||||||
|
msgArgs = listOf(stabilizeCount, stabilizeTarget))
|
||||||
}
|
}
|
||||||
phase = Phase4.LR_BALANCE
|
phase = Phase4.LR_BALANCE
|
||||||
}
|
}
|
||||||
@@ -235,7 +255,10 @@ class AlignGuide4Stage(
|
|||||||
val recoveryDir = if (3 in lost) AlignAction.MOVE_UP else AlignAction.MOVE_DOWN
|
val recoveryDir = if (3 in lost) AlignAction.MOVE_UP else AlignAction.MOVE_DOWN
|
||||||
val arrow = if (recoveryDir == AlignAction.MOVE_UP) "↑" else "↓"
|
val arrow = if (recoveryDir == AlignAction.MOVE_UP) "↑" else "↓"
|
||||||
val dirWord = if (recoveryDir == AlignAction.MOVE_UP) "위" else "아래"
|
val dirWord = if (recoveryDir == AlignAction.MOVE_UP) "위" else "아래"
|
||||||
return Out(recoveryDir, "$arrow 위치 살짝 벗어남 — ${dirWord}로 약간", phase, 0)
|
val recoveryKey = if (recoveryDir == AlignAction.MOVE_UP)
|
||||||
|
R.string.align_recover_up_short else R.string.align_recover_down_short
|
||||||
|
return Out(recoveryDir, "$arrow 위치 살짝 벗어남 — ${dirWord}로 약간", phase, 0,
|
||||||
|
msgKey = recoveryKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// full set 유지 중 — 좌우 균형 (deadband + consecutive 악화 로 oscillation 차단)
|
// full set 유지 중 — 좌우 균형 (deadband + consecutive 악화 로 oscillation 차단)
|
||||||
@@ -245,7 +268,8 @@ class AlignGuide4Stage(
|
|||||||
val imbal = kotlin.math.abs(u4 - u5)
|
val imbal = kotlin.math.abs(u4 - u5)
|
||||||
if (imbal <= latTol) {
|
if (imbal <= latTol) {
|
||||||
return Out(AlignAction.STOP,
|
return Out(AlignAction.STOP,
|
||||||
"✓ 정렬 완료! 그대로 측정 가능", phase, 0)
|
"✓ 정렬 완료! 그대로 측정 가능", phase, 0,
|
||||||
|
msgKey = R.string.align_complete)
|
||||||
}
|
}
|
||||||
if (lateralDir == null) {
|
if (lateralDir == null) {
|
||||||
// 첫 진입 — u4/u5 비교로 방향 추정
|
// 첫 진입 — u4/u5 비교로 방향 추정
|
||||||
@@ -270,10 +294,14 @@ class AlignGuide4Stage(
|
|||||||
lateralImbal = imbal
|
lateralImbal = imbal
|
||||||
val arrow = if (lateralDir == AlignAction.MOVE_LEFT) "←" else "→"
|
val arrow = if (lateralDir == AlignAction.MOVE_LEFT) "←" else "→"
|
||||||
val sideWord = if (lateralDir == AlignAction.MOVE_LEFT) "왼쪽" else "오른쪽"
|
val sideWord = if (lateralDir == AlignAction.MOVE_LEFT) "왼쪽" else "오른쪽"
|
||||||
return Out(lateralDir!!, "$arrow ${sideWord}으로 살짝", phase, 0)
|
val sideKey = if (lateralDir == AlignAction.MOVE_LEFT)
|
||||||
|
R.string.align_left else R.string.align_right
|
||||||
|
return Out(lateralDir!!, "$arrow ${sideWord}으로 살짝", phase, 0,
|
||||||
|
msgKey = sideKey)
|
||||||
}
|
}
|
||||||
// u4/u5 미검출 → probe
|
// u4/u5 미검출 → probe
|
||||||
return Out(AlignAction.PROBE_LR, "↔ 좌우로 천천히 움직여 보세요", phase, 0)
|
return Out(AlignAction.PROBE_LR, "↔ 좌우로 천천히 움직여 보세요", phase, 0,
|
||||||
|
msgKey = R.string.align_probe_lr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +357,7 @@ class RollingAligner(
|
|||||||
val state = if (out.phase == AlignGuide4Stage.Phase4.INITIAL_ACCUM) "accum" else "commit"
|
val state = if (out.phase == AlignGuide4Stage.Phase4.INITIAL_ACCUM) "accum" else "commit"
|
||||||
val st = AdvisorState(
|
val st = AdvisorState(
|
||||||
state = state, action = out.action, msg = out.msg,
|
state = state, action = out.action, msg = out.msg,
|
||||||
|
msgKey = out.msgKey, msgArgs = out.msgArgs,
|
||||||
dets = dets, summary = summary,
|
dets = dets, summary = summary,
|
||||||
accumProgress = if (state == "accum") out.progress else accumK,
|
accumProgress = if (state == "accum") out.progress else accumK,
|
||||||
accumTarget = accumK,
|
accumTarget = accumK,
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class BleForegroundService : Service() {
|
|||||||
"VesiScan BLE Connection",
|
"VesiScan BLE Connection",
|
||||||
NotificationManager.IMPORTANCE_LOW
|
NotificationManager.IMPORTANCE_LOW
|
||||||
).apply {
|
).apply {
|
||||||
description = "BLE 기기 연결 유지"
|
description = getString(R.string.ble_service_channel_desc)
|
||||||
setShowBadge(false)
|
setShowBadge(false)
|
||||||
}
|
}
|
||||||
val nm = getSystemService(NotificationManager::class.java)
|
val nm = getSystemService(NotificationManager::class.java)
|
||||||
@@ -127,7 +127,7 @@ class BleForegroundService : Service() {
|
|||||||
|
|
||||||
return NotificationCompat.Builder(this, CHANNEL_ID)
|
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
.setContentTitle("VesiScan")
|
.setContentTitle("VesiScan")
|
||||||
.setContentText("BLE 기기 연결 중...")
|
.setContentText(getString(R.string.ble_service_connecting))
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class ReminderService private constructor(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private const val CHANNEL_ID = "catheter_reminder"
|
private const val CHANNEL_ID = "catheter_reminder"
|
||||||
private const val CHANNEL_NAME = "배뇨 알림"
|
|
||||||
private const val NOTIFICATION_ID = 2001
|
private const val NOTIFICATION_ID = 2001
|
||||||
private const val REQUEST_CODE = 3001
|
private const val REQUEST_CODE = 3001
|
||||||
private const val PREFS_NAME = "reminder_prefs"
|
private const val PREFS_NAME = "reminder_prefs"
|
||||||
@@ -148,19 +147,19 @@ class ReminderService private constructor(private val context: Context) {
|
|||||||
alarmManager.cancel(pendingIntent)
|
alarmManager.cancel(pendingIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val reminderMessages = listOf(
|
private val reminderMessageIds = listOf(
|
||||||
"배뇨 시간입니다! 도뇨 준비를 해주세요.",
|
R.string.reminder_msg_1,
|
||||||
"규칙적인 배뇨는 건강의 기본입니다!",
|
R.string.reminder_msg_2,
|
||||||
"화장실에 다녀오세요! 💧",
|
R.string.reminder_msg_3,
|
||||||
"배뇨 시간이에요. 건강한 습관을 유지하세요!",
|
R.string.reminder_msg_4,
|
||||||
"잊지 마세요! 화장실 갈 시간입니다."
|
R.string.reminder_msg_5,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun showNotification() {
|
fun showNotification() {
|
||||||
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||||
.setContentTitle("VesiScan 알림")
|
.setContentTitle(context.getString(R.string.notification_title))
|
||||||
.setContentText(reminderMessages.random())
|
.setContentText(context.getString(reminderMessageIds.random()))
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
|
|
||||||
@@ -198,9 +197,9 @@ class ReminderService private constructor(private val context: Context) {
|
|||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val channel = NotificationChannel(
|
val channel = NotificationChannel(
|
||||||
CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH
|
CHANNEL_ID, context.getString(R.string.notification_channel_name), NotificationManager.IMPORTANCE_HIGH
|
||||||
).apply {
|
).apply {
|
||||||
description = "배뇨 알리미 알림"
|
description = context.getString(R.string.notification_channel_desc)
|
||||||
enableVibration(true)
|
enableVibration(true)
|
||||||
}
|
}
|
||||||
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|||||||
+5
-3
@@ -18,9 +18,11 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.ble.BleManager
|
import com.example.medilightv2android.ble.BleManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,11 +43,11 @@ fun BatteryWarningBanner(bleManager: BleManager, modifier: Modifier = Modifier)
|
|||||||
|
|
||||||
val critical = batt <= CRITICAL_BATTERY_THRESHOLD
|
val critical = batt <= CRITICAL_BATTERY_THRESHOLD
|
||||||
val bgColor = if (critical) Color(0xFFD32F2F) else Color(0xFFFF9500)
|
val bgColor = if (critical) Color(0xFFD32F2F) else Color(0xFFFF9500)
|
||||||
val title = if (critical) "기기 배터리 위급" else "기기 배터리 부족"
|
val title = if (critical) stringResource(R.string.battery_critical_title) else stringResource(R.string.battery_low_title)
|
||||||
val subtitle = if (critical)
|
val subtitle = if (critical)
|
||||||
"현재 ${batt}% · 측정이 갑자기 중단될 수 있습니다. 즉시 충전하세요."
|
stringResource(R.string.battery_critical_desc, batt)
|
||||||
else
|
else
|
||||||
"현재 ${batt}% · 곧 충전이 필요합니다."
|
stringResource(R.string.battery_low_desc, batt)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|||||||
+4
-2
@@ -22,9 +22,11 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.ble.BleManager
|
import com.example.medilightv2android.ble.BleManager
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
@@ -75,11 +77,11 @@ fun FirmwareWarningBanner(bleManager: BleManager, modifier: Modifier = Modifier)
|
|||||||
if (!outdated && !showTimedOut) return
|
if (!outdated && !showTimedOut) return
|
||||||
|
|
||||||
val bgColor = if (outdated) Color(0xFFFF9500) else Color(0xFFD32F2F)
|
val bgColor = if (outdated) Color(0xFFFF9500) else Color(0xFFD32F2F)
|
||||||
val title = if (outdated) "펌웨어 업데이트 필요" else "기기 응답 없음"
|
val title = if (outdated) stringResource(R.string.firmware_outdated_title) else stringResource(R.string.firmware_no_response_title)
|
||||||
// timedOut(mid? 미수신) 케이스는 사용자에게 mid?/펌웨어 호환 같은 기술 용어가 무의미.
|
// timedOut(mid? 미수신) 케이스는 사용자에게 mid?/펌웨어 호환 같은 기술 용어가 무의미.
|
||||||
// outdated 일 때만 상세 안내, timedOut 은 타이틀만 노출.
|
// outdated 일 때만 상세 안내, timedOut 은 타이틀만 노출.
|
||||||
val subtitle: String? = if (outdated) {
|
val subtitle: String? = if (outdated) {
|
||||||
"현재: $fw · 필요: VBTFW${"%04d".format(BleManager.MIN_FIRMWARE_BUILD)} 이상. 측정이 정상 동작하지 않을 수 있습니다."
|
stringResource(R.string.firmware_outdated_desc, fw, BleManager.MIN_FIRMWARE_BUILD)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.example.medilightv2android.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LockableSlider(
|
fun LockableSlider(
|
||||||
@@ -57,7 +59,7 @@ fun LockableSlider(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
if (unlocked) Icons.Default.LockOpen else Icons.Default.Lock,
|
if (unlocked) Icons.Default.LockOpen else Icons.Default.Lock,
|
||||||
contentDescription = if (unlocked) "잠금 해제됨 — 다시 탭하여 잠금" else "잠김 — 탭하여 해제 후 조정",
|
contentDescription = if (unlocked) stringResource(R.string.slider_unlocked_desc) else stringResource(R.string.slider_locked_desc),
|
||||||
tint = if (unlocked) Color(0xFF4CAF50) else Color.Gray.copy(alpha = 0.5f),
|
tint = if (unlocked) Color(0xFF4CAF50) else Color.Gray.copy(alpha = 0.5f),
|
||||||
modifier = Modifier.size(18.dp)
|
modifier = Modifier.size(18.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-4
@@ -29,11 +29,13 @@ import androidx.compose.ui.draw.shadow
|
|||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.example.medilightv2android.AppState
|
import com.example.medilightv2android.AppState
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.ble.BleDevice
|
import com.example.medilightv2android.ble.BleDevice
|
||||||
import com.example.medilightv2android.ble.BleManager
|
import com.example.medilightv2android.ble.BleManager
|
||||||
import com.example.medilightv2android.models.SensorMode
|
import com.example.medilightv2android.models.SensorMode
|
||||||
@@ -342,17 +344,17 @@ fun DeviceScanView(appState: AppState) {
|
|||||||
pendingDelete?.let { target ->
|
pendingDelete?.let { target ->
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { pendingDelete = null },
|
onDismissRequest = { pendingDelete = null },
|
||||||
title = { Text("페어링 해제") },
|
title = { Text(stringResource(R.string.unpair_title)) },
|
||||||
text = { Text("${target.name} 의 페어링을 해제합니다. 다시 사용하려면 PIN 재입력이 필요합니다.") },
|
text = { Text(stringResource(R.string.unpair_message, target.name)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
bleManager.unbondAndRemoveAddress(target.address)
|
bleManager.unbondAndRemoveAddress(target.address)
|
||||||
knownDevices = KnownDeviceStore.getAll(context)
|
knownDevices = KnownDeviceStore.getAll(context)
|
||||||
pendingDelete = null
|
pendingDelete = null
|
||||||
}) { Text("해제", color = Color.Red) }
|
}) { Text(stringResource(R.string.unpair_confirm), color = Color.Red) }
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { pendingDelete = null }) { Text("취소") }
|
TextButton(onClick = { pendingDelete = null }) { Text(stringResource(R.string.cancel)) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.medilightv2android.AppState
|
import com.example.medilightv2android.AppState
|
||||||
@@ -47,13 +49,13 @@ fun ConsentView(appState: AppState) {
|
|||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
"이용 동의",
|
stringResource(R.string.consent_title),
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.padding(horizontal = 32.dp)
|
modifier = Modifier.padding(horizontal = 32.dp)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"서비스 이용을 위해 아래 항목에 동의해 주세요.",
|
stringResource(R.string.consent_intro),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = MlSecondaryText,
|
color = MlSecondaryText,
|
||||||
modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)
|
modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)
|
||||||
@@ -91,28 +93,28 @@ fun ConsentView(appState: AppState) {
|
|||||||
colors = CheckboxDefaults.colors(checkedColor = MlPrimary)
|
colors = CheckboxDefaults.colors(checkedColor = MlPrimary)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text("전체 동의", fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.consent_agree_all), fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider(color = MlSecondaryText.copy(alpha = 0.2f))
|
HorizontalDivider(color = MlSecondaryText.copy(alpha = 0.2f))
|
||||||
|
|
||||||
ConsentItem(
|
ConsentItem(
|
||||||
title = "[필수] 개인정보 수집 및 이용 동의",
|
title = stringResource(R.string.consent_personal_title),
|
||||||
description = "이름, 나이, 성별, 신체정보(키·체중)를 앱 내 기능 제공 목적으로 수집합니다. 수집된 정보는 기기에만 저장되며 외부로 전송되지 않습니다.",
|
description = stringResource(R.string.consent_personal_desc),
|
||||||
checked = consentPersonal,
|
checked = consentPersonal,
|
||||||
onCheckedChange = { consentPersonal = it }
|
onCheckedChange = { consentPersonal = it }
|
||||||
)
|
)
|
||||||
|
|
||||||
ConsentItem(
|
ConsentItem(
|
||||||
title = "[필수] 의료정보 활용 동의",
|
title = stringResource(R.string.consent_medical_title),
|
||||||
description = "방광 측정 데이터, 배뇨일지, NIRS 산소포화도 등 건강 관련 데이터를 모니터링 및 기록 목적으로 활용합니다.",
|
description = stringResource(R.string.consent_medical_desc),
|
||||||
checked = consentMedical,
|
checked = consentMedical,
|
||||||
onCheckedChange = { consentMedical = it }
|
onCheckedChange = { consentMedical = it }
|
||||||
)
|
)
|
||||||
|
|
||||||
ConsentItem(
|
ConsentItem(
|
||||||
title = "[필수] 데이터 저장 동의",
|
title = stringResource(R.string.consent_data_title),
|
||||||
description = "측정 기록 및 배뇨일지 데이터가 기기 내부 저장소에 저장됩니다. 앱 삭제 시 모든 데이터가 삭제됩니다.",
|
description = stringResource(R.string.consent_data_desc),
|
||||||
checked = consentData,
|
checked = consentData,
|
||||||
onCheckedChange = { consentData = it }
|
onCheckedChange = { consentData = it }
|
||||||
)
|
)
|
||||||
@@ -149,7 +151,7 @@ fun ConsentView(appState: AppState) {
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"동의하고 계속하기",
|
stringResource(R.string.consent_continue),
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = Color.White
|
color = Color.White
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.medilightv2android.AppState
|
import com.example.medilightv2android.AppState
|
||||||
@@ -167,13 +169,13 @@ fun HomeView(appState: AppState) {
|
|||||||
var selected by remember { mutableStateOf(curMode) }
|
var selected by remember { mutableStateOf(curMode) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showLanguageDialog = false },
|
onDismissRequest = { showLanguageDialog = false },
|
||||||
title = { Text("Language / 언어") },
|
title = { Text(stringResource(R.string.language_dialog_title)) },
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
listOf(
|
listOf(
|
||||||
com.example.medilightv2android.services.LocalePref.Mode.SYSTEM to "시스템 기본 / System default",
|
com.example.medilightv2android.services.LocalePref.Mode.SYSTEM to stringResource(R.string.language_system_default),
|
||||||
com.example.medilightv2android.services.LocalePref.Mode.KO to "한국어",
|
com.example.medilightv2android.services.LocalePref.Mode.KO to stringResource(R.string.language_korean),
|
||||||
com.example.medilightv2android.services.LocalePref.Mode.EN to "English",
|
com.example.medilightv2android.services.LocalePref.Mode.EN to stringResource(R.string.language_english),
|
||||||
).forEach { (mode, label) ->
|
).forEach { (mode, label) ->
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -198,10 +200,10 @@ fun HomeView(appState: AppState) {
|
|||||||
showLanguageDialog = false
|
showLanguageDialog = false
|
||||||
// Activity recreate → attachBaseContext 다시 호출 → 새 locale 적용
|
// Activity recreate → attachBaseContext 다시 호출 → 새 locale 적용
|
||||||
(context as? android.app.Activity)?.recreate()
|
(context as? android.app.Activity)?.recreate()
|
||||||
}) { Text("OK") }
|
}) { Text(stringResource(R.string.confirm)) }
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showLanguageDialog = false }) { Text("Cancel") }
|
TextButton(onClick = { showLanguageDialog = false }) { Text(stringResource(R.string.cancel)) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -175,17 +175,17 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Icon(Icons.Default.Warning, contentDescription = null,
|
Icon(Icons.Default.Warning, contentDescription = null,
|
||||||
tint = Color.White, modifier = Modifier.size(56.dp))
|
tint = Color.White, modifier = Modifier.size(56.dp))
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text("환자가 일어났습니다", fontSize = 28.sp,
|
Text(stringResource(R.string.alarm_patient_woke_up), fontSize = 28.sp,
|
||||||
fontWeight = FontWeight.ExtraBold, color = Color.White,
|
fontWeight = FontWeight.ExtraBold, color = Color.White,
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center)
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
Text("Patient is getting up", fontSize = 16.sp,
|
Text(stringResource(R.string.alarm_patient_woke_up_en), fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.Medium, color = Color.White.copy(alpha = 0.9f),
|
fontWeight = FontWeight.Medium, color = Color.White.copy(alpha = 0.9f),
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
val mins = wakeupAlarmElapsedSec / 60
|
val mins = wakeupAlarmElapsedSec / 60
|
||||||
val secs = wakeupAlarmElapsedSec % 60
|
val secs = wakeupAlarmElapsedSec % 60
|
||||||
Text("누운 시간 ${mins}분 ${secs}초",
|
Text(stringResource(R.string.alarm_lying_duration, mins, secs),
|
||||||
fontSize = 14.sp, color = Color.White.copy(alpha = 0.85f))
|
fontSize = 14.sp, color = Color.White.copy(alpha = 0.85f))
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Button(
|
Button(
|
||||||
@@ -196,7 +196,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||||
shape = RoundedCornerShape(14.dp)
|
shape = RoundedCornerShape(14.dp)
|
||||||
) {
|
) {
|
||||||
Text("확인", fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.confirm), fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,16 +223,16 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Icon(Icons.Default.DirectionsWalk, contentDescription = null,
|
Icon(Icons.Default.DirectionsWalk, contentDescription = null,
|
||||||
tint = Color.White, modifier = Modifier.size(64.dp))
|
tint = Color.White, modifier = Modifier.size(64.dp))
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text("환자가 걷고 있습니다", fontSize = 30.sp,
|
Text(stringResource(R.string.alarm_patient_walking), fontSize = 30.sp,
|
||||||
fontWeight = FontWeight.ExtraBold, color = Color.White,
|
fontWeight = FontWeight.ExtraBold, color = Color.White,
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center)
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
Text("Patient is walking — fall risk",
|
Text(stringResource(R.string.alarm_patient_walking_en),
|
||||||
fontSize = 16.sp, fontWeight = FontWeight.Medium,
|
fontSize = 16.sp, fontWeight = FontWeight.Medium,
|
||||||
color = Color.White.copy(alpha = 0.92f),
|
color = Color.White.copy(alpha = 0.92f),
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text("즉시 환자 상태 확인 필요",
|
Text(stringResource(R.string.alarm_immediate_check),
|
||||||
fontSize = 14.sp, color = Color.White.copy(alpha = 0.9f),
|
fontSize = 14.sp, color = Color.White.copy(alpha = 0.9f),
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center)
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
@@ -244,7 +244,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||||
shape = RoundedCornerShape(14.dp)
|
shape = RoundedCornerShape(14.dp)
|
||||||
) {
|
) {
|
||||||
Text("확인", fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.confirm), fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -856,7 +856,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ��뇨 기록 입력 방식 선택 (iOS DiaryRecordSheet 스타일)
|
// 배뇨 기록 입력 방식 선택 (iOS DiaryRecordSheet 스타일)
|
||||||
if (showCatheterizeSheet) {
|
if (showCatheterizeSheet) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showCatheterizeSheet = false },
|
onDismissRequest = { showCatheterizeSheet = false },
|
||||||
@@ -868,7 +868,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
"${appState.estimatedVolume} mL",
|
"${appState.estimatedVolume} mL",
|
||||||
fontSize = 28.sp, fontWeight = FontWeight.Bold, color = MlPrimary
|
fontSize = 28.sp, fontWeight = FontWeight.Bold, color = MlPrimary
|
||||||
)
|
)
|
||||||
Text("추정 용량", fontSize = 12.sp, color = MlSecondaryText)
|
Text(stringResource(R.string.estimated_volume), fontSize = 12.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
@@ -1926,8 +1926,8 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
Row(verticalAlignment = Alignment.CenterVertically,
|
Row(verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth()) {
|
modifier = Modifier.fillMaxWidth()) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text("기상 알람", fontSize = labelSize, fontWeight = FontWeight.SemiBold)
|
Text(stringResource(R.string.wakeup_alarm_title), fontSize = labelSize, fontWeight = FontWeight.SemiBold)
|
||||||
Text("일어남 감지 시 빨간 팝업",
|
Text(stringResource(R.string.wakeup_alarm_desc),
|
||||||
fontSize = 11.sp, color = MlSecondaryText)
|
fontSize = 11.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
Switch(
|
Switch(
|
||||||
|
|||||||
+15
-8
@@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.drawscope.rotate
|
|||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
@@ -45,6 +46,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.medilightv2android.AppScreen
|
import com.example.medilightv2android.AppScreen
|
||||||
import com.example.medilightv2android.AppState
|
import com.example.medilightv2android.AppState
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.ble.BleManager
|
import com.example.medilightv2android.ble.BleManager
|
||||||
import com.example.medilightv2android.ble.PiezoChannelData
|
import com.example.medilightv2android.ble.PiezoChannelData
|
||||||
import com.example.medilightv2android.managers.DetachmentDetection
|
import com.example.medilightv2android.managers.DetachmentDetection
|
||||||
@@ -202,15 +204,15 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
Icon(Icons.Default.Warning, contentDescription = null,
|
Icon(Icons.Default.Warning, contentDescription = null,
|
||||||
tint = Color.White, modifier = Modifier.size(56.dp))
|
tint = Color.White, modifier = Modifier.size(56.dp))
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text("센서가 분리되었습니다", fontSize = 26.sp,
|
Text(stringResource(R.string.sensor_detached_title), fontSize = 26.sp,
|
||||||
fontWeight = FontWeight.ExtraBold, color = Color.White,
|
fontWeight = FontWeight.ExtraBold, color = Color.White,
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center)
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
Text("Sensor detached", fontSize = 16.sp,
|
Text(stringResource(R.string.sensor_detached_subtitle), fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.Medium, color = Color.White.copy(alpha = 0.9f),
|
fontWeight = FontWeight.Medium, color = Color.White.copy(alpha = 0.9f),
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text("치골 위쪽에 다시 위치시켜 주세요",
|
Text(stringResource(R.string.sensor_detached_desc),
|
||||||
fontSize = 14.sp, color = Color.White.copy(alpha = 0.85f),
|
fontSize = 14.sp, color = Color.White.copy(alpha = 0.85f),
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center)
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
@@ -222,7 +224,7 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||||
shape = RoundedCornerShape(14.dp)
|
shape = RoundedCornerShape(14.dp)
|
||||||
) {
|
) {
|
||||||
Text("확인", fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.confirm), fontSize = 20.sp, fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,7 +355,8 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
// 점 ... 애니메이션 — 알고리즘 진행 중인 메시지에 점 추가.
|
// 점 ... 애니메이션 — 알고리즘 진행 중인 메시지에 점 추가.
|
||||||
if (hint.startsWith("Slide") || hint.startsWith("Optimizing") ||
|
if (hint.startsWith("Slide") || hint.startsWith("Optimizing") ||
|
||||||
hint.startsWith("Checking") || hint.startsWith("Adjust") ||
|
hint.startsWith("Checking") || hint.startsWith("Adjust") ||
|
||||||
hint.contains("이동") || hint.contains("천천히") || hint.contains("그대로")) {
|
hint.contains("이동") || hint.contains("천천히") || hint.contains("그대로") ||
|
||||||
|
hint.contains("Hold") || hint.contains("slowly") || hint.contains("Slightly")) {
|
||||||
append(dots)
|
append(dots)
|
||||||
}
|
}
|
||||||
if (appState.isDevMode && !lastCv.isNaN()) {
|
if (appState.isDevMode && !lastCv.isNaN()) {
|
||||||
@@ -860,7 +863,7 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
// Clinical alignment session + waitingForStart 일 땐 알고리즘 skip —
|
// Clinical alignment session + waitingForStart 일 땐 알고리즘 skip —
|
||||||
// baseline cycles 만 누적 (Start 누르기 전 자동 phase 진행 차단).
|
// baseline cycles 만 누적 (Start 누르기 전 자동 phase 진행 차단).
|
||||||
if (waitingForStart) {
|
if (waitingForStart) {
|
||||||
directionHint = "센서를 치골 위에 두고 Start 를 누르세요"
|
directionHint = context.getString(R.string.press_start_hint)
|
||||||
directionIcon = ""
|
directionIcon = ""
|
||||||
placementScore = 0
|
placementScore = 0
|
||||||
} else {
|
} else {
|
||||||
@@ -880,12 +883,16 @@ fun PlacementGuideView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
bleManager.debugLogger.info("ALIGN_V2 phase=${v2Aligner.phase} state=${st.state} action=${st.action} u4=${st.summary?.u4} u5=${st.summary?.u5} ch3=${st.summary?.ch3} bestSet=${v2Aligner.bestCenterSet}")
|
bleManager.debugLogger.info("ALIGN_V2 phase=${v2Aligner.phase} state=${st.state} action=${st.action} u4=${st.summary?.u4} u5=${st.summary?.u5} ch3=${st.summary?.ch3} bestSet=${v2Aligner.bestCenterSet}")
|
||||||
|
// Localized message via string resource (falls back to st.msg for dev builds)
|
||||||
|
val localizedMsg = st.msgKey?.let { key ->
|
||||||
|
context.getString(key, *st.msgArgs.toTypedArray())
|
||||||
|
} ?: st.msg
|
||||||
if (st.state == "accum") {
|
if (st.state == "accum") {
|
||||||
directionHint = st.msg
|
directionHint = localizedMsg
|
||||||
directionIcon = ""
|
directionIcon = ""
|
||||||
placementScore = 5
|
placementScore = 5
|
||||||
} else {
|
} else {
|
||||||
directionHint = st.msg
|
directionHint = localizedMsg
|
||||||
// STOP 액션은 두 경우 — CH3_STABILIZE 의 "위치 유지" (still in progress) vs
|
// STOP 액션은 두 경우 — CH3_STABILIZE 의 "위치 유지" (still in progress) vs
|
||||||
// LR_BALANCE 의 "정렬 완료" (truly done). 후자만 finalStop 으로 GREEN lock.
|
// LR_BALANCE 의 "정렬 완료" (truly done). 후자만 finalStop 으로 GREEN lock.
|
||||||
val finalStop = st.action == com.example.medilightv2android.managers.AlignAction.STOP &&
|
val finalStop = st.action == com.example.medilightv2android.managers.AlignAction.STOP &&
|
||||||
|
|||||||
+10
-10
@@ -235,16 +235,16 @@ private fun SummaryBar(records: List<VoidingRecord>) {
|
|||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
Icon(Icons.Default.WaterDrop, contentDescription = null, Modifier.size(14.dp), Color(0xFF2196F3))
|
Icon(Icons.Default.WaterDrop, contentDescription = null, Modifier.size(14.dp), Color(0xFF2196F3))
|
||||||
Text("${count}회", fontSize = 12.sp, color = Color(0xFF2196F3))
|
Text(stringResource(R.string.count_times, count), fontSize = 12.sp, color = Color(0xFF2196F3))
|
||||||
}
|
}
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
Icon(Icons.Default.Science, contentDescription = null, Modifier.size(14.dp), Color(0xFF00BCD4))
|
Icon(Icons.Default.Science, contentDescription = null, Modifier.size(14.dp), Color(0xFF00BCD4))
|
||||||
Text("${total} ml", fontSize = 12.sp, color = Color(0xFF00BCD4))
|
Text(stringResource(R.string.volume_ml_short, total), fontSize = 12.sp, color = Color(0xFF00BCD4))
|
||||||
}
|
}
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
Icon(Icons.Default.BarChart, contentDescription = null, Modifier.size(14.dp), Color(0xFF4CAF50))
|
Icon(Icons.Default.BarChart, contentDescription = null, Modifier.size(14.dp), Color(0xFF4CAF50))
|
||||||
Text("평균 ${avg} ml", fontSize = 12.sp, color = Color(0xFF4CAF50))
|
Text(stringResource(R.string.average_ml, avg), fontSize = 12.sp, color = Color(0xFF4CAF50))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,20 +258,20 @@ private fun MedicalMetricsCard(metrics: DiaryMetrics) {
|
|||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
Text("24시간 의료 지표", fontSize = 14.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text(stringResource(R.string.medical_metrics_24h), fontSize = 14.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
HorizontalDivider(color = MlSecondaryText.copy(alpha = 0.15f))
|
HorizontalDivider(color = MlSecondaryText.copy(alpha = 0.15f))
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
MetricCell("24hUV", "${metrics.totalVolume24h} ml", Modifier.weight(1f))
|
MetricCell("24hUV", stringResource(R.string.volume_ml_short, metrics.totalVolume24h), Modifier.weight(1f))
|
||||||
MetricCell("NUV", "${metrics.nocturnalVolume} ml", Modifier.weight(1f))
|
MetricCell("NUV", stringResource(R.string.volume_ml_short, metrics.nocturnalVolume), Modifier.weight(1f))
|
||||||
MetricCell("NPI", String.format("%.0f%%", metrics.npi * 100), Modifier.weight(1f),
|
MetricCell("NPI", String.format("%.0f%%", metrics.npi * 100), Modifier.weight(1f),
|
||||||
highlight = metrics.npi > 0.33)
|
highlight = metrics.npi > 0.33)
|
||||||
MetricCell("Max", "${metrics.maxCapacity} ml", Modifier.weight(1f))
|
MetricCell("Max", stringResource(R.string.volume_ml_short, metrics.maxCapacity), Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
MetricCell("총 횟수", "${metrics.totalCount}회", Modifier.weight(1f))
|
MetricCell(stringResource(R.string.metric_total_count), stringResource(R.string.count_times, metrics.totalCount), Modifier.weight(1f))
|
||||||
MetricCell("주간", "${metrics.daytimeCount}회", Modifier.weight(1f))
|
MetricCell(stringResource(R.string.metric_daytime), stringResource(R.string.count_times, metrics.daytimeCount), Modifier.weight(1f))
|
||||||
MetricCell("야간", "${metrics.nighttimeCount}회", Modifier.weight(1f),
|
MetricCell(stringResource(R.string.metric_nighttime), stringResource(R.string.count_times, metrics.nighttimeCount), Modifier.weight(1f),
|
||||||
highlight = metrics.nighttimeCount >= 2)
|
highlight = metrics.nighttimeCount >= 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -21,10 +21,12 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.medilightv2android.AppState
|
import com.example.medilightv2android.AppState
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.ble.BleManager
|
import com.example.medilightv2android.ble.BleManager
|
||||||
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
|
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
|
||||||
import com.example.medilightv2android.ui.theme.*
|
import com.example.medilightv2android.ui.theme.*
|
||||||
@@ -172,7 +174,7 @@ private fun CatheterThresholdCard(appState: AppState) {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text("Voiding Reminder", fontSize = 28.sp, fontWeight = FontWeight.ExtraBold)
|
Text("Voiding Reminder", fontSize = 28.sp, fontWeight = FontWeight.ExtraBold)
|
||||||
Text("방광이 이 정도 차오르면 알림",
|
Text(stringResource(R.string.voiding_reminder_threshold_desc),
|
||||||
fontSize = 12.sp, color = MlSecondaryText,
|
fontSize = 12.sp, color = MlSecondaryText,
|
||||||
modifier = Modifier.padding(top = 2.dp))
|
modifier = Modifier.padding(top = 2.dp))
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
@@ -354,13 +356,13 @@ private fun WakeupAlarmCard() {
|
|||||||
modifier = androidx.compose.ui.Modifier.weight(1f)
|
modifier = androidx.compose.ui.Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.Text(
|
androidx.compose.material3.Text(
|
||||||
"기상 알람", fontSize = 24.sp, fontWeight = FontWeight.ExtraBold
|
stringResource(R.string.wakeup_alarm_title), fontSize = 24.sp, fontWeight = FontWeight.ExtraBold
|
||||||
)
|
)
|
||||||
androidx.compose.foundation.layout.Spacer(
|
androidx.compose.foundation.layout.Spacer(
|
||||||
modifier = androidx.compose.ui.Modifier.height(4.dp)
|
modifier = androidx.compose.ui.Modifier.height(4.dp)
|
||||||
)
|
)
|
||||||
androidx.compose.material3.Text(
|
androidx.compose.material3.Text(
|
||||||
"환자가 일어나면 빨간 팝업으로 알림 (낙상 예방)",
|
stringResource(R.string.wakeup_alarm_desc),
|
||||||
fontSize = 12.sp, color = MlSecondaryText
|
fontSize = 12.sp, color = MlSecondaryText
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -13,11 +13,13 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.medilightv2android.AppState
|
import com.example.medilightv2android.AppState
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.models.Sex
|
import com.example.medilightv2android.models.Sex
|
||||||
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
|
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
|
||||||
import com.example.medilightv2android.ui.theme.*
|
import com.example.medilightv2android.ui.theme.*
|
||||||
@@ -72,7 +74,7 @@ fun RegistrationView(appState: AppState) {
|
|||||||
Box(modifier = Modifier.weight(1f)) {
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
RegistrationTextField("Age", ageText, { ageText = it }, KeyboardType.Number)
|
RegistrationTextField("Age", ageText, { ageText = it }, KeyboardType.Number)
|
||||||
}
|
}
|
||||||
listOf(Sex.MALE to "남성", Sex.FEMALE to "여성").forEach { (sex, label) ->
|
listOf(Sex.MALE to stringResource(R.string.sex_male), Sex.FEMALE to stringResource(R.string.sex_female)).forEach { (sex, label) ->
|
||||||
val selected = selectedSex == sex
|
val selected = selectedSex == sex
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { selectedSex = sex },
|
onClick = { selectedSex = sex },
|
||||||
|
|||||||
+30
-28
@@ -16,9 +16,11 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.example.medilightv2android.R
|
||||||
import com.example.medilightv2android.services.ReminderService
|
import com.example.medilightv2android.services.ReminderService
|
||||||
import com.example.medilightv2android.ui.components.LockableSlider
|
import com.example.medilightv2android.ui.components.LockableSlider
|
||||||
import com.example.medilightv2android.ui.theme.*
|
import com.example.medilightv2android.ui.theme.*
|
||||||
@@ -43,10 +45,10 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("배뇨 알리미", fontWeight = FontWeight.SemiBold) },
|
title = { Text(stringResource(R.string.reminder_title), fontWeight = FontWeight.SemiBold) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "뒤로")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -70,10 +72,10 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text("알림 활성화", fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.reminder_enable), fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
||||||
Text(
|
Text(
|
||||||
if (isEnabled) "정해진 간격으로 배뇨 알림을 받습니다"
|
if (isEnabled) stringResource(R.string.reminder_enabled_desc)
|
||||||
else "알림이 꺼져 있습니다",
|
else stringResource(R.string.reminder_disabled_desc),
|
||||||
fontSize = 12.sp, color = MlSecondaryText
|
fontSize = 12.sp, color = MlSecondaryText
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -95,7 +97,7 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text("알림 간격", fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.reminder_interval_label), fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
intervalOptions.forEach { minutes ->
|
intervalOptions.forEach { minutes ->
|
||||||
Row(
|
Row(
|
||||||
@@ -110,7 +112,7 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
formatInterval(minutes),
|
formatInterval(minutes, context),
|
||||||
fontSize = 15.sp,
|
fontSize = 15.sp,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
@@ -131,12 +133,12 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text("알림 시간대", fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.reminder_active_hours), fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text("시작", fontSize = 13.sp, color = MlSecondaryText, modifier = Modifier.width(40.dp))
|
Text(stringResource(R.string.reminder_start), fontSize = 13.sp, color = MlSecondaryText, modifier = Modifier.width(40.dp))
|
||||||
Text("${startHour}시", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text(stringResource(R.string.reminder_hour_format, startHour), fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
LockableSlider(
|
LockableSlider(
|
||||||
value = startHour.toFloat(),
|
value = startHour.toFloat(),
|
||||||
@@ -157,8 +159,8 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text("종료", fontSize = 13.sp, color = MlSecondaryText, modifier = Modifier.width(40.dp))
|
Text(stringResource(R.string.reminder_end), fontSize = 13.sp, color = MlSecondaryText, modifier = Modifier.width(40.dp))
|
||||||
Text("${endHour}시", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
Text(stringResource(R.string.reminder_hour_format, endHour), fontSize = 18.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
}
|
}
|
||||||
LockableSlider(
|
LockableSlider(
|
||||||
value = endHour.toFloat(),
|
value = endHour.toFloat(),
|
||||||
@@ -184,8 +186,8 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text("유동 스케줄링", fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.reminder_flexible_title), fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
||||||
Text("마지막 기록 기준으로 다음 알림을 조정합니다", fontSize = 12.sp, color = MlSecondaryText)
|
Text(stringResource(R.string.reminder_flexible_desc), fontSize = 12.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
Switch(
|
Switch(
|
||||||
checked = isFlexible,
|
checked = isFlexible,
|
||||||
@@ -205,13 +207,13 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text("알림 방식", fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.reminder_method_label), fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text("소리", modifier = Modifier.weight(1f))
|
Text(stringResource(R.string.reminder_sound), modifier = Modifier.weight(1f))
|
||||||
Switch(
|
Switch(
|
||||||
checked = useSound,
|
checked = useSound,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
@@ -226,7 +228,7 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text("진동", modifier = Modifier.weight(1f))
|
Text(stringResource(R.string.reminder_vibration), modifier = Modifier.weight(1f))
|
||||||
Switch(
|
Switch(
|
||||||
checked = useVibration,
|
checked = useVibration,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
@@ -241,7 +243,7 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text("팝업", modifier = Modifier.weight(1f))
|
Text(stringResource(R.string.reminder_popup), modifier = Modifier.weight(1f))
|
||||||
Switch(
|
Switch(
|
||||||
checked = usePopup,
|
checked = usePopup,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
@@ -261,14 +263,14 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
Text("요약", fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.reminder_summary), fontSize = 14.sp, fontWeight = FontWeight.Bold)
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
Text("🕐", fontSize = 14.sp)
|
Text("🕐", fontSize = 14.sp)
|
||||||
Text("활성 시간: ${startHour}시 ~ ${endHour}시", fontSize = 14.sp, color = MlPrimary)
|
Text(stringResource(R.string.reminder_active_hours_summary, startHour, endHour), fontSize = 14.sp, color = MlPrimary)
|
||||||
}
|
}
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
Text("🔁", fontSize = 14.sp)
|
Text("🔁", fontSize = 14.sp)
|
||||||
Text("매 ${formatInterval(intervalMinutes)} 간격", fontSize = 14.sp, color = Color(0xFFF5A623))
|
Text(stringResource(R.string.reminder_interval_summary, formatInterval(intervalMinutes, context)), fontSize = 14.sp, color = Color(0xFFF5A623))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,8 +289,8 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
Text("⚠️", fontSize = 20.sp)
|
Text("⚠️", fontSize = 20.sp)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text("알림 권한 필요", fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
Text(stringResource(R.string.reminder_permission_needed), fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||||
Text("설정에서 알림 권한을 허용해주세요", fontSize = 12.sp, color = MlSecondaryText)
|
Text(stringResource(R.string.reminder_permission_desc), fontSize = 12.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
OutlinedButton(onClick = {
|
OutlinedButton(onClick = {
|
||||||
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
|
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
|
||||||
@@ -296,7 +298,7 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
}
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}) {
|
}) {
|
||||||
Text("설정")
|
Text(stringResource(R.string.settings))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,12 +309,12 @@ fun ReminderSettingsView(onBack: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatInterval(minutes: Int): String {
|
private fun formatInterval(minutes: Int, context: android.content.Context): String {
|
||||||
val h = minutes / 60
|
val h = minutes / 60
|
||||||
val m = minutes % 60
|
val m = minutes % 60
|
||||||
return when {
|
return when {
|
||||||
h > 0 && m > 0 -> "${h}시간 ${m}분"
|
h > 0 && m > 0 -> context.getString(R.string.format_hours_minutes, h, m)
|
||||||
h > 0 -> "${h}시간"
|
h > 0 -> context.getString(R.string.format_hours, h)
|
||||||
else -> "${m}분"
|
else -> context.getString(R.string.format_minutes, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,4 +68,127 @@
|
|||||||
<string name="too_dark">이미지가 너무 어둡습니다</string>
|
<string name="too_dark">이미지가 너무 어둡습니다</string>
|
||||||
<string name="not_enough_marks">눈금 부족</string>
|
<string name="not_enough_marks">눈금 부족</string>
|
||||||
<string name="detection_fail">감지 실패</string>
|
<string name="detection_fail">감지 실패</string>
|
||||||
|
|
||||||
|
<!-- Home / Language Dialog -->
|
||||||
|
<string name="language_dialog_title">언어</string>
|
||||||
|
<string name="language_system_default">시스템 기본</string>
|
||||||
|
<string name="language_korean">한국어</string>
|
||||||
|
<string name="language_english">English</string>
|
||||||
|
|
||||||
|
<!-- Consent -->
|
||||||
|
<string name="consent_title">이용 동의</string>
|
||||||
|
<string name="consent_intro">서비스 이용을 위해 아래 항목에 동의해 주세요.</string>
|
||||||
|
<string name="consent_agree_all">전체 동의</string>
|
||||||
|
<string name="consent_personal_title">[필수] 개인정보 수집 및 이용 동의</string>
|
||||||
|
<string name="consent_personal_desc">이름, 나이, 성별, 신체정보(키·체중)를 앱 내 기능 제공 목적으로 수집합니다. 수집된 정보는 기기에만 저장되며 외부로 전송되지 않습니다.</string>
|
||||||
|
<string name="consent_medical_title">[필수] 의료정보 활용 동의</string>
|
||||||
|
<string name="consent_medical_desc">방광 측정 데이터, 배뇨일지, NIRS 산소포화도 등 건강 관련 데이터를 모니터링 및 기록 목적으로 활용합니다.</string>
|
||||||
|
<string name="consent_data_title">[필수] 데이터 저장 동의</string>
|
||||||
|
<string name="consent_data_desc">측정 기록 및 배뇨일지 데이터가 기기 내부 저장소에 저장됩니다. 앱 삭제 시 모든 데이터가 삭제됩니다.</string>
|
||||||
|
<string name="consent_continue">동의하고 계속하기</string>
|
||||||
|
|
||||||
|
<!-- Registration -->
|
||||||
|
<string name="sex_male">남성</string>
|
||||||
|
<string name="sex_female">여성</string>
|
||||||
|
|
||||||
|
<!-- Reminder Settings -->
|
||||||
|
<string name="reminder_title">배뇨 알리미</string>
|
||||||
|
<string name="reminder_enable">알림 활성화</string>
|
||||||
|
<string name="reminder_enabled_desc">정해진 간격으로 배뇨 알림을 받습니다</string>
|
||||||
|
<string name="reminder_disabled_desc">알림이 꺼져 있습니다</string>
|
||||||
|
<string name="reminder_interval_label">알림 간격</string>
|
||||||
|
<string name="reminder_active_hours">알림 시간대</string>
|
||||||
|
<string name="reminder_start">시작</string>
|
||||||
|
<string name="reminder_end">종료</string>
|
||||||
|
<string name="reminder_hour_format">%d시</string>
|
||||||
|
<string name="reminder_flexible_title">유동 스케줄링</string>
|
||||||
|
<string name="reminder_flexible_desc">마지막 기록 기준으로 다음 알림을 조정합니다</string>
|
||||||
|
<string name="reminder_method_label">알림 방식</string>
|
||||||
|
<string name="reminder_sound">소리</string>
|
||||||
|
<string name="reminder_vibration">진동</string>
|
||||||
|
<string name="reminder_popup">팝업</string>
|
||||||
|
<string name="reminder_summary">요약</string>
|
||||||
|
<string name="reminder_active_hours_summary">활성 시간: %1$d시 ~ %2$d시</string>
|
||||||
|
<string name="reminder_interval_summary">매 %s 간격</string>
|
||||||
|
<string name="reminder_permission_needed">알림 권한 필요</string>
|
||||||
|
<string name="reminder_permission_desc">설정에서 알림 권한을 허용해주세요</string>
|
||||||
|
<string name="settings">설정</string>
|
||||||
|
<string name="format_hours_minutes">%1$d시간 %2$d분</string>
|
||||||
|
<string name="format_hours">%d시간</string>
|
||||||
|
<string name="format_minutes">%d분</string>
|
||||||
|
|
||||||
|
<!-- Voiding Diary additions -->
|
||||||
|
<string name="average_ml">평균 %d ml</string>
|
||||||
|
<string name="count_times">%d회</string>
|
||||||
|
<string name="volume_ml_short">%d ml</string>
|
||||||
|
<string name="medical_metrics_24h">24시간 의료 지표</string>
|
||||||
|
<string name="metric_total_count">총 횟수</string>
|
||||||
|
<string name="metric_daytime">주간</string>
|
||||||
|
<string name="metric_nighttime">야간</string>
|
||||||
|
|
||||||
|
<!-- Placement Guide / Sensor Detach -->
|
||||||
|
<string name="sensor_detached_title">센서가 분리되었습니다</string>
|
||||||
|
<string name="sensor_detached_subtitle">Sensor detached</string>
|
||||||
|
<string name="sensor_detached_desc">치골 위쪽에 다시 위치시켜 주세요</string>
|
||||||
|
<string name="press_start_hint">센서를 치골 위에 두고 Start 를 누르세요</string>
|
||||||
|
|
||||||
|
<!-- Wakeup / Walking Alarm -->
|
||||||
|
<string name="alarm_patient_woke_up">환자가 일어났습니다</string>
|
||||||
|
<string name="alarm_patient_woke_up_en">Patient is getting up</string>
|
||||||
|
<string name="alarm_lying_duration">누운 시간 %1$d분 %2$d초</string>
|
||||||
|
<string name="alarm_patient_walking">환자가 걷고 있습니다</string>
|
||||||
|
<string name="alarm_patient_walking_en">Patient is walking — fall risk</string>
|
||||||
|
<string name="alarm_immediate_check">즉시 환자 상태 확인 필요</string>
|
||||||
|
|
||||||
|
<!-- Voiding Record additions -->
|
||||||
|
<string name="estimated_volume">추정 용량</string>
|
||||||
|
|
||||||
|
<!-- Device Scan -->
|
||||||
|
<string name="unpair_title">페어링 해제</string>
|
||||||
|
<string name="unpair_message">%s 의 페어링을 해제합니다. 다시 사용하려면 PIN 재입력이 필요합니다.</string>
|
||||||
|
<string name="unpair_confirm">해제</string>
|
||||||
|
|
||||||
|
<!-- Reminder Notifications -->
|
||||||
|
<string name="notification_channel_name">배뇨 알림</string>
|
||||||
|
<string name="notification_channel_desc">배뇨 알리미 알림</string>
|
||||||
|
<string name="notification_title">VesiScan 알림</string>
|
||||||
|
<string name="reminder_msg_1">배뇨 시간입니다! 도뇨 준비를 해주세요.</string>
|
||||||
|
<string name="reminder_msg_2">규칙적인 배뇨는 건강의 기본입니다!</string>
|
||||||
|
<string name="reminder_msg_3">화장실에 다녀오세요! 💧</string>
|
||||||
|
<string name="reminder_msg_4">배뇨 시간이에요. 건강한 습관을 유지하세요!</string>
|
||||||
|
<string name="reminder_msg_5">잊지 마세요! 화장실 갈 시간입니다.</string>
|
||||||
|
|
||||||
|
<!-- BLE Foreground Service -->
|
||||||
|
<string name="ble_service_channel_desc">BLE 기기 연결 유지</string>
|
||||||
|
<string name="ble_service_connecting">BLE 기기 연결 중...</string>
|
||||||
|
|
||||||
|
<!-- Battery / Firmware Warnings -->
|
||||||
|
<string name="battery_critical_title">기기 배터리 위급</string>
|
||||||
|
<string name="battery_low_title">기기 배터리 부족</string>
|
||||||
|
<string name="battery_critical_desc">현재 %d%% · 측정이 갑자기 중단될 수 있습니다. 즉시 충전하세요.</string>
|
||||||
|
<string name="battery_low_desc">현재 %d%% · 곧 충전이 필요합니다.</string>
|
||||||
|
<string name="firmware_outdated_title">펌웨어 업데이트 필요</string>
|
||||||
|
<string name="firmware_no_response_title">기기 응답 없음</string>
|
||||||
|
<string name="firmware_outdated_desc">현재: %1$s · 필요: VBTFW%2$04d 이상. 측정이 정상 동작하지 않을 수 있습니다.</string>
|
||||||
|
|
||||||
|
<!-- Lockable Slider -->
|
||||||
|
<string name="slider_unlocked_desc">잠금 해제됨 — 다시 탭하여 잠금</string>
|
||||||
|
<string name="slider_locked_desc">잠김 — 탭하여 해제 후 조정</string>
|
||||||
|
|
||||||
|
<!-- Personalization -->
|
||||||
|
<string name="wakeup_alarm_title">기상 알람</string>
|
||||||
|
<string name="wakeup_alarm_desc">환자가 일어나면 빨간 팝업으로 알림 (낙상 예방)</string>
|
||||||
|
<string name="voiding_reminder_threshold_desc">방광이 이 정도 차오르면 알림</string>
|
||||||
|
|
||||||
|
<!-- Alignment Advisor V2 hints -->
|
||||||
|
<string name="align_hold_still">잠시 그대로 두세요 (%1$d/%2$d)</string>
|
||||||
|
<string name="align_move_up">↑ 위로 조금씩 올리세요</string>
|
||||||
|
<string name="align_recover_up">↑ 살짝 놓침 — 다시 위로</string>
|
||||||
|
<string name="align_hold_position">✓ 좋아요! 그 위치 유지 (%1$d/%2$d)</string>
|
||||||
|
<string name="align_recover_up_short">↑ 위치 살짝 벗어남 — 위로 약간</string>
|
||||||
|
<string name="align_recover_down_short">↓ 위치 살짝 벗어남 — 아래로 약간</string>
|
||||||
|
<string name="align_complete">✓ 정렬 완료! 그대로 측정 가능</string>
|
||||||
|
<string name="align_left">← 왼쪽으로 살짝</string>
|
||||||
|
<string name="align_right">→ 오른쪽으로 살짝</string>
|
||||||
|
<string name="align_probe_lr">↔ 좌우로 천천히 움직여 보세요</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -68,4 +68,127 @@
|
|||||||
<string name="too_dark">Image is too dark</string>
|
<string name="too_dark">Image is too dark</string>
|
||||||
<string name="not_enough_marks">Not enough marks</string>
|
<string name="not_enough_marks">Not enough marks</string>
|
||||||
<string name="detection_fail">Detection failed</string>
|
<string name="detection_fail">Detection failed</string>
|
||||||
|
|
||||||
|
<!-- Home / Language Dialog -->
|
||||||
|
<string name="language_dialog_title">Language</string>
|
||||||
|
<string name="language_system_default">System default</string>
|
||||||
|
<string name="language_korean">Korean</string>
|
||||||
|
<string name="language_english">English</string>
|
||||||
|
|
||||||
|
<!-- Consent -->
|
||||||
|
<string name="consent_title">User Agreement</string>
|
||||||
|
<string name="consent_intro">Please agree to the items below to use this service.</string>
|
||||||
|
<string name="consent_agree_all">Agree to all</string>
|
||||||
|
<string name="consent_personal_title">[Required] Personal information collection</string>
|
||||||
|
<string name="consent_personal_desc">We collect name, age, sex, and body information (height, weight) for in-app functionality. The collected data is stored only on the device and is not transmitted externally.</string>
|
||||||
|
<string name="consent_medical_title">[Required] Medical data use</string>
|
||||||
|
<string name="consent_medical_desc">Bladder measurement data, voiding diary, NIRS oxygen saturation, and other health-related data are used for monitoring and record purposes.</string>
|
||||||
|
<string name="consent_data_title">[Required] Data storage</string>
|
||||||
|
<string name="consent_data_desc">Measurement records and voiding diary data are stored in the device internal storage. All data is deleted when the app is uninstalled.</string>
|
||||||
|
<string name="consent_continue">Agree and continue</string>
|
||||||
|
|
||||||
|
<!-- Registration -->
|
||||||
|
<string name="sex_male">Male</string>
|
||||||
|
<string name="sex_female">Female</string>
|
||||||
|
|
||||||
|
<!-- Reminder Settings -->
|
||||||
|
<string name="reminder_title">Voiding Reminder</string>
|
||||||
|
<string name="reminder_enable">Enable reminders</string>
|
||||||
|
<string name="reminder_enabled_desc">You will receive voiding reminders at the set interval</string>
|
||||||
|
<string name="reminder_disabled_desc">Reminders are turned off</string>
|
||||||
|
<string name="reminder_interval_label">Reminder interval</string>
|
||||||
|
<string name="reminder_active_hours">Active hours</string>
|
||||||
|
<string name="reminder_start">Start</string>
|
||||||
|
<string name="reminder_end">End</string>
|
||||||
|
<string name="reminder_hour_format">%d:00</string>
|
||||||
|
<string name="reminder_flexible_title">Flexible scheduling</string>
|
||||||
|
<string name="reminder_flexible_desc">Adjusts the next reminder based on the most recent record</string>
|
||||||
|
<string name="reminder_method_label">Notification method</string>
|
||||||
|
<string name="reminder_sound">Sound</string>
|
||||||
|
<string name="reminder_vibration">Vibration</string>
|
||||||
|
<string name="reminder_popup">Popup</string>
|
||||||
|
<string name="reminder_summary">Summary</string>
|
||||||
|
<string name="reminder_active_hours_summary">Active: %1$d:00 – %2$d:00</string>
|
||||||
|
<string name="reminder_interval_summary">Every %s</string>
|
||||||
|
<string name="reminder_permission_needed">Notification permission required</string>
|
||||||
|
<string name="reminder_permission_desc">Please allow notifications in Settings</string>
|
||||||
|
<string name="settings">Settings</string>
|
||||||
|
<string name="format_hours_minutes">%1$dh %2$dm</string>
|
||||||
|
<string name="format_hours">%dh</string>
|
||||||
|
<string name="format_minutes">%dm</string>
|
||||||
|
|
||||||
|
<!-- Voiding Diary additions -->
|
||||||
|
<string name="average_ml">Avg %d ml</string>
|
||||||
|
<string name="count_times">%d times</string>
|
||||||
|
<string name="volume_ml_short">%d ml</string>
|
||||||
|
<string name="medical_metrics_24h">24-hour Medical Metrics</string>
|
||||||
|
<string name="metric_total_count">Total</string>
|
||||||
|
<string name="metric_daytime">Day</string>
|
||||||
|
<string name="metric_nighttime">Night</string>
|
||||||
|
|
||||||
|
<!-- Placement Guide / Sensor Detach -->
|
||||||
|
<string name="sensor_detached_title">Sensor detached</string>
|
||||||
|
<string name="sensor_detached_subtitle">Sensor detached</string>
|
||||||
|
<string name="sensor_detached_desc">Please reposition above the pubic bone</string>
|
||||||
|
<string name="press_start_hint">Place the sensor above the pubic bone and press Start</string>
|
||||||
|
|
||||||
|
<!-- Wakeup / Walking Alarm -->
|
||||||
|
<string name="alarm_patient_woke_up">Patient is getting up</string>
|
||||||
|
<string name="alarm_patient_woke_up_en">Patient is getting up</string>
|
||||||
|
<string name="alarm_lying_duration">Lying for %1$d min %2$d sec</string>
|
||||||
|
<string name="alarm_patient_walking">Patient is walking</string>
|
||||||
|
<string name="alarm_patient_walking_en">Patient is walking — fall risk</string>
|
||||||
|
<string name="alarm_immediate_check">Immediate patient check required</string>
|
||||||
|
|
||||||
|
<!-- Voiding Record additions -->
|
||||||
|
<string name="estimated_volume">Estimated volume</string>
|
||||||
|
|
||||||
|
<!-- Device Scan -->
|
||||||
|
<string name="unpair_title">Unpair Device</string>
|
||||||
|
<string name="unpair_message">Unpair %s. PIN re-entry will be required to use it again.</string>
|
||||||
|
<string name="unpair_confirm">Unpair</string>
|
||||||
|
|
||||||
|
<!-- Reminder Notifications -->
|
||||||
|
<string name="notification_channel_name">Voiding reminder</string>
|
||||||
|
<string name="notification_channel_desc">Voiding reminder notifications</string>
|
||||||
|
<string name="notification_title">VesiScan reminder</string>
|
||||||
|
<string name="reminder_msg_1">Time to void! Please prepare for catheterization.</string>
|
||||||
|
<string name="reminder_msg_2">Regular voiding is fundamental to health!</string>
|
||||||
|
<string name="reminder_msg_3">Time for the bathroom! 💧</string>
|
||||||
|
<string name="reminder_msg_4">It is time to void. Keep up healthy habits!</string>
|
||||||
|
<string name="reminder_msg_5">Don\'t forget! It is time to go to the bathroom.</string>
|
||||||
|
|
||||||
|
<!-- BLE Foreground Service -->
|
||||||
|
<string name="ble_service_channel_desc">Maintains BLE device connection</string>
|
||||||
|
<string name="ble_service_connecting">Connecting to BLE device...</string>
|
||||||
|
|
||||||
|
<!-- Battery / Firmware Warnings -->
|
||||||
|
<string name="battery_critical_title">Critical battery</string>
|
||||||
|
<string name="battery_low_title">Low battery</string>
|
||||||
|
<string name="battery_critical_desc">%d%% · Measurement may stop suddenly. Charge immediately.</string>
|
||||||
|
<string name="battery_low_desc">%d%% · Please charge soon.</string>
|
||||||
|
<string name="firmware_outdated_title">Firmware update required</string>
|
||||||
|
<string name="firmware_no_response_title">No response from device</string>
|
||||||
|
<string name="firmware_outdated_desc">Current: %1$s · Required: VBTFW%2$04d or higher. Measurement may not work properly.</string>
|
||||||
|
|
||||||
|
<!-- Lockable Slider -->
|
||||||
|
<string name="slider_unlocked_desc">Unlocked — tap again to lock</string>
|
||||||
|
<string name="slider_locked_desc">Locked — tap to unlock and adjust</string>
|
||||||
|
|
||||||
|
<!-- Personalization -->
|
||||||
|
<string name="wakeup_alarm_title">Wake-up alarm</string>
|
||||||
|
<string name="wakeup_alarm_desc">Red popup when patient gets up (fall prevention)</string>
|
||||||
|
<string name="voiding_reminder_threshold_desc">Alert when bladder fills to this level</string>
|
||||||
|
|
||||||
|
<!-- Alignment Advisor V2 hints -->
|
||||||
|
<string name="align_hold_still">Hold still… (%1$d/%2$d)</string>
|
||||||
|
<string name="align_move_up">↑ Slide up slowly</string>
|
||||||
|
<string name="align_recover_up">↑ Lost it — slide back up</string>
|
||||||
|
<string name="align_hold_position">✓ Good! Keep this position (%1$d/%2$d)</string>
|
||||||
|
<string name="align_recover_up_short">↑ Slightly off — move up a bit</string>
|
||||||
|
<string name="align_recover_down_short">↓ Slightly off — move down a bit</string>
|
||||||
|
<string name="align_complete">✓ Aligned! Ready to measure</string>
|
||||||
|
<string name="align_left">← Slightly to the left</string>
|
||||||
|
<string name="align_right">→ Slightly to the right</string>
|
||||||
|
<string name="align_probe_lr">↔ Move slowly side to side</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user