feat(locale): DeviceScanView 한글화 — 기기 연결 페이지

이전 sweep 에서 누락됐던 두번째 '기기 연결' 화면 풀 한글화.

추가 strings: 25 신규 (각 언어)
- '기기 연결' 제목, 검색/탭 안내
- 페어링 필요 알림 (다이얼로그 + 에러 메시지)
- Bluetooth off / 권한 필요 경고
- 검색 중 / 기기 없음 / 트러블슈팅 4단계
- 저장된 기기 / 새 기기 섹션
- 권한 허용 / 검색 시작·중지 버튼
- 최근, 페어링 삭제, 신호 좋음/보통/약함

설계:
- signalLabel(rssi) → 영어 — dev/log 용 유지
- signalLabelRes(rssi) 신규 — UI 용 Locale-aware 라벨

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 11:31:50 +09:00
parent 3b37c816fc
commit 6cff741370
3 changed files with 97 additions and 28 deletions
@@ -133,7 +133,7 @@ fun DeviceScanView(appState: AppState) {
appState.deviceConnected()
} else {
bleManager.disconnect()
bleManager.connectionError.value = "Pairing required. Please pair the device."
bleManager.connectionError.value = context.getString(R.string.device_scan_pairing_required_err)
}
}
}
@@ -149,8 +149,8 @@ fun DeviceScanView(appState: AppState) {
if (showPairingHint) {
AlertDialog(
onDismissRequest = {},
title = { Text("Pairing Required", fontWeight = FontWeight.Bold) },
text = { Text("A Bluetooth pairing request will appear.\nTap 'Pair', then enter PIN if prompted.") },
title = { Text(stringResource(R.string.device_scan_pairing_required_title), fontWeight = FontWeight.Bold) },
text = { Text(stringResource(R.string.device_scan_pairing_required_msg)) },
confirmButton = {}
)
}
@@ -166,7 +166,7 @@ fun DeviceScanView(appState: AppState) {
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = { appState.backToSensorSelect() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = MlPrimary)
Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.device_scan_back_desc), tint = MlPrimary)
}
Spacer(modifier = Modifier.weight(1f))
}
@@ -175,11 +175,11 @@ fun DeviceScanView(appState: AppState) {
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Connect Device", fontSize = 24.sp, fontWeight = FontWeight.Bold)
Text(stringResource(R.string.device_scan_title), fontSize = 24.sp, fontWeight = FontWeight.Bold)
Spacer(modifier = Modifier.height(8.dp))
Text(
if (knownDevices.isEmpty()) "Searching for nearby devices..."
else "Tap a saved device or scan for new",
if (knownDevices.isEmpty()) stringResource(R.string.device_scan_searching)
else stringResource(R.string.device_scan_tap_or_scan),
fontSize = 14.sp, color = MlSecondaryText
)
}
@@ -197,10 +197,10 @@ fun DeviceScanView(appState: AppState) {
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Warning, "Warning", tint = Color.White)
Icon(Icons.Default.Warning, stringResource(R.string.device_scan_warning_desc), tint = Color.White)
Spacer(modifier = Modifier.width(12.dp))
Column {
Text("Bluetooth is off", fontWeight = FontWeight.Bold, color = Color.White, fontSize = 14.sp)
Text(stringResource(R.string.device_scan_bluetooth_off), fontWeight = FontWeight.Bold, color = Color.White, fontSize = 14.sp)
Text("Enable Bluetooth in Settings", color = Color.White.copy(alpha = 0.8f), fontSize = 12.sp)
}
}
@@ -218,10 +218,10 @@ fun DeviceScanView(appState: AppState) {
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Warning, "Warning", tint = Color.White)
Icon(Icons.Default.Warning, stringResource(R.string.device_scan_warning_desc), tint = Color.White)
Spacer(modifier = Modifier.width(12.dp))
Column {
Text("Permissions required", fontWeight = FontWeight.Bold, color = Color.White, fontSize = 14.sp)
Text(stringResource(R.string.device_scan_permissions_required), fontWeight = FontWeight.Bold, color = Color.White, fontSize = 14.sp)
Text("Bluetooth and Location permissions are needed to scan for devices", color = Color.White.copy(alpha = 0.8f), fontSize = 12.sp)
}
}
@@ -239,11 +239,11 @@ fun DeviceScanView(appState: AppState) {
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Close, "Error", tint = Color.White)
Icon(Icons.Default.Close, stringResource(R.string.device_scan_error_desc), tint = Color.White)
Spacer(modifier = Modifier.width(12.dp))
Text(error, color = Color.White, fontSize = 13.sp, modifier = Modifier.weight(1f))
IconButton(onClick = { bleManager.connectionError.value = null }) {
Icon(Icons.Default.Close, "Dismiss", tint = Color.White.copy(alpha = 0.8f))
Icon(Icons.Default.Close, stringResource(R.string.device_scan_dismiss_desc), tint = Color.White.copy(alpha = 0.8f))
}
}
Spacer(modifier = Modifier.height(8.dp))
@@ -259,7 +259,7 @@ fun DeviceScanView(appState: AppState) {
) {
BladdyIdleRiveView(size = 180.dp)
Spacer(modifier = Modifier.height(16.dp))
Text("Scanning...", color = MlSecondaryText)
Text(stringResource(R.string.device_scan_scanning), color = MlSecondaryText)
}
} else if (emptyAll) {
Column(
@@ -269,17 +269,17 @@ fun DeviceScanView(appState: AppState) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("No devices found", fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText)
Text(stringResource(R.string.device_scan_no_devices), fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = MlSecondaryText)
Spacer(modifier = Modifier.height(16.dp))
Card(
shape = RoundedCornerShape(14.dp),
colors = CardDefaults.cardColors(containerColor = MlCardBackground)
) {
Column(modifier = Modifier.padding(16.dp)) {
TroubleshootItem("1.", "Make sure the device is powered on")
TroubleshootItem("2.", "Keep the device within 1 meter")
TroubleshootItem("3.", "Check Bluetooth is enabled in Settings")
TroubleshootItem("4.", "Try restarting the device")
TroubleshootItem("1.", stringResource(R.string.device_scan_troubleshoot_1))
TroubleshootItem("2.", stringResource(R.string.device_scan_troubleshoot_2))
TroubleshootItem("3.", stringResource(R.string.device_scan_troubleshoot_3))
TroubleshootItem("4.", stringResource(R.string.device_scan_troubleshoot_4))
}
}
if (appState.isDevMode) {
@@ -293,7 +293,7 @@ fun DeviceScanView(appState: AppState) {
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = MlPrimary.copy(alpha = 0.15f))
) {
Text("Enter Measurement Mode (Dev)", color = MlPrimary, fontWeight = FontWeight.SemiBold)
Text(stringResource(R.string.device_scan_enter_measurement_dev), color = MlPrimary, fontWeight = FontWeight.SemiBold)
}
}
}
@@ -305,7 +305,7 @@ fun DeviceScanView(appState: AppState) {
) {
if (knownDevices.isNotEmpty()) {
item("known_header") {
SectionHeader("Saved Devices", knownDevices.size)
SectionHeader(stringResource(R.string.device_scan_saved_devices), knownDevices.size)
}
items(knownDevices, key = { "k_${it.address}" }) { known ->
KnownDeviceRow(
@@ -323,7 +323,7 @@ fun DeviceScanView(appState: AppState) {
if (newDevices.isNotEmpty()) {
item("new_header") {
Spacer(modifier = Modifier.height(8.dp))
SectionHeader("New Devices", newDevices.size)
SectionHeader(stringResource(R.string.device_scan_new_devices), newDevices.size)
}
items(newDevices, key = { "n_${it.address}" }) { device ->
DeviceRow(
@@ -390,9 +390,9 @@ fun DeviceScanView(appState: AppState) {
) {
Text(
when {
!permissionsGranted -> "Grant Permissions"
isScanning -> "Stop Scan"
else -> "Start Scan"
!permissionsGranted -> stringResource(R.string.device_scan_grant_permissions)
isScanning -> stringResource(R.string.device_scan_stop_scan)
else -> stringResource(R.string.device_scan_start_scan)
},
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
@@ -451,7 +451,7 @@ private fun KnownDeviceRow(
Spacer(modifier = Modifier.width(12.dp))
Column(modifier = Modifier.weight(1f)) {
Text(device.name, fontSize = 15.sp, fontWeight = FontWeight.SemiBold)
Text("Last: $lastConnectedLabel", fontSize = 11.sp, color = MlSecondaryText)
Text(stringResource(R.string.device_scan_last_label, lastConnectedLabel), fontSize = 11.sp, color = MlSecondaryText)
}
if (isConnecting) {
CircularProgressIndicator(modifier = Modifier.size(22.dp), strokeWidth = 2.dp)
@@ -461,7 +461,7 @@ private fun KnownDeviceRow(
enabled = enabled,
modifier = Modifier.size(36.dp)
) {
Icon(Icons.Default.Delete, "Delete pairing",
Icon(Icons.Default.Delete, stringResource(R.string.device_scan_delete_pairing_desc),
tint = Color.Red.copy(alpha = 0.7f),
modifier = Modifier.size(18.dp))
}
@@ -501,7 +501,7 @@ private fun DeviceRow(device: BleDevice, isConnecting: Boolean, enabled: Boolean
Row(verticalAlignment = Alignment.CenterVertically) {
SignalBars(rssi = device.rssi)
Spacer(modifier = Modifier.width(4.dp))
Text(signalLabel(device.rssi), fontSize = 12.sp, color = MlSecondaryText)
Text(stringResource(signalLabelRes(device.rssi)), fontSize = 12.sp, color = MlSecondaryText)
}
}
@@ -545,6 +545,13 @@ private fun signalLabel(rssi: Int): String = when {
else -> "Weak"
}
/** UI 용 — Locale 적용된 라벨. signalLabel(rssi) 는 dev/log 용으로 유지. */
private fun signalLabelRes(rssi: Int): Int = when {
rssi >= -80 -> R.string.device_scan_signal_good
rssi >= -90 -> R.string.device_scan_signal_fair
else -> R.string.device_scan_signal_weak
}
@Composable
private fun TroubleshootItem(number: String, text: String) {
Row(modifier = Modifier.padding(vertical = 4.dp)) {