ui(dfu): 펌웨어 업데이트 화면 앱 스타일 통일 · 한글화 · 상태칩
미니멈 리디자인 (기존 3카드 구조 유지):
· 배경 = MlBackground · 카드 = MlCardBackground · RoundedCornerShape 16
· 헤드라인 "MEDiDFU" 제거 (앱 브랜드와 무관)
· 카드 label 한글화: "Firmware (bundled)" → "펌웨어 정보" ·
"Devices (n)" → "발견된 기기 (n)" · "Log" → "상세 로그"
· Phase 사용자 한글 라벨 + 색상 dot 상태칩:
IDLE→"대기 중" (회색) · SCANNING→"기기 검색 중…" (주황) ·
CONNECTED→"연결됨 · 업데이트 준비 완료" (녹색) ·
UPLOADING→"업데이트 진행 중…" (파랑) · COMPLETED→"완료 ✓" (녹색) ·
FAILED→"실패" (빨강) · ENTERING_DFU/SEARCHING_DFU 포함
· 주 액션 "업데이트 시작" = MlTeal→MlPrimary gradient primary 버튼
(앱 다른 primary 버튼 · Onboarding "Get started" 와 동일 스타일)
· 보조 액션 (검색/중지/연결 해제/취소/초기화) = outlined + MlPrimary 톤 ·
취소는 MlCritical 톤
· 진행률: 8dp 굵은 LinearProgressIndicator · MlPrimary 색 ·
Row 로 좌 (퍼센트 SemiBold) / 우 (KB / KB 표기)
· DeviceRow: 이름 SemiBold · address 제거 (RSSI 만) · "DFU 모드" 칩 MlTeal ·
"Connect" → "연결" · outlined 스타일
· 권한 안내: 문구 MlSecondaryText · 버튼도 gradient primary
· 로그: 카드 배경 MlSecondaryText 8% alpha (연한 회색) · 텍스트 MlSecondaryText
Import 추가: MlPrimary/MlTeal/MlBackground/MlCardBackground/MlSecondaryText/
MlCritical/MlSuccess/MlWarning · ButtonDefaults · Brush · PaddingValues.
빌드: BUILD SUCCESSFUL. (폰 미연결 · install 은 재연결 후 별도.)
This commit is contained in:
@@ -16,6 +16,7 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -25,6 +26,16 @@ import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import com.medithings.vesiscan.ui.theme.MlBackground
|
||||
import com.medithings.vesiscan.ui.theme.MlCardBackground
|
||||
import com.medithings.vesiscan.ui.theme.MlCritical
|
||||
import com.medithings.vesiscan.ui.theme.MlPrimary
|
||||
import com.medithings.vesiscan.ui.theme.MlSecondaryText
|
||||
import com.medithings.vesiscan.ui.theme.MlSuccess
|
||||
import com.medithings.vesiscan.ui.theme.MlTeal
|
||||
import com.medithings.vesiscan.ui.theme.MlWarning
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
@@ -113,13 +124,13 @@ fun FirmwareUpdateScreen(onBack: () -> Unit, modifier: Modifier = Modifier) {
|
||||
}
|
||||
DisposableEffect(Unit) { onDispose { manager.shutdown() } }
|
||||
|
||||
Column(modifier = modifier.fillMaxSize()) {
|
||||
Column(modifier = modifier.fillMaxSize().background(MlBackground)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "뒤로")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "뒤로", tint = MlPrimary)
|
||||
}
|
||||
Text("펌웨어 업데이트", style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold)
|
||||
@@ -135,13 +146,64 @@ fun FirmwareUpdateScreen(onBack: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
"펌웨어 업데이트에는 Bluetooth 권한이 필요합니다.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MlSecondaryText,
|
||||
)
|
||||
Button(onClick = { launcher.launch(perms) }) { Text("권한 허용") }
|
||||
PrimaryGradientButton(text = "권한 허용", onClick = { launcher.launch(perms) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 앱 스타일 통일 primary 버튼 (MlTeal → MlPrimary gradient · rounded 14). */
|
||||
@Composable
|
||||
private fun PrimaryGradientButton(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
enabled: Boolean = true,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
modifier = modifier.heightIn(min = 48.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
disabledContainerColor = Color.Transparent,
|
||||
),
|
||||
contentPadding = PaddingValues(),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 48.dp)
|
||||
.background(
|
||||
brush = if (enabled)
|
||||
Brush.horizontalGradient(listOf(MlTeal, MlPrimary))
|
||||
else Brush.horizontalGradient(listOf(Color(0xFFCCCCCC), Color(0xFFAAAAAA))),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
)
|
||||
.padding(horizontal = 20.dp, vertical = 12.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(text, color = Color.White, fontWeight = FontWeight.SemiBold, fontSize = 15.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Phase → 사용자용 한글 라벨 + 상태 색상. */
|
||||
private fun phaseKo(phase: DfuPhase): Pair<String, Color> = when (phase) {
|
||||
DfuPhase.IDLE -> "대기 중" to MlSecondaryText
|
||||
DfuPhase.SCANNING -> "기기 검색 중…" to MlWarning
|
||||
DfuPhase.CONNECTING -> "연결 중…" to MlWarning
|
||||
DfuPhase.CONNECTED -> "연결됨 · 업데이트 준비 완료" to MlSuccess
|
||||
DfuPhase.ENTERING_DFU -> "DFU 모드 진입 중…" to MlPrimary
|
||||
DfuPhase.SEARCHING_DFU -> "DFU 모드 확인 중…" to MlPrimary
|
||||
DfuPhase.UPLOADING -> "업데이트 진행 중…" to MlPrimary
|
||||
DfuPhase.COMPLETED -> "완료 ✓" to MlSuccess
|
||||
DfuPhase.FAILED -> "실패" to MlCritical
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DfuScreen(manager: DfuManager, modifier: Modifier = Modifier) {
|
||||
val phase by manager.phase
|
||||
@@ -152,133 +214,200 @@ fun DfuScreen(manager: DfuManager, modifier: Modifier = Modifier) {
|
||||
val bytesSent by manager.bytesSent
|
||||
val target by manager.target
|
||||
|
||||
val cardShape = RoundedCornerShape(16.dp)
|
||||
val cardColors = CardDefaults.cardColors(containerColor = MlCardBackground)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(MlBackground)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text("MEDiDFU", style = MaterialTheme.typography.headlineSmall)
|
||||
|
||||
// ── Anything outside the app that would silently break scanning ───────────────────
|
||||
// ── 스캔 실패 원인 안내 (블루투스 꺼짐 등) ────────────────────────────────────────
|
||||
val blocker = manager.bleBlockingCondition()
|
||||
if (blocker != null) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
),
|
||||
shape = cardShape,
|
||||
colors = CardDefaults.cardColors(containerColor = MlCritical.copy(alpha = 0.10f)),
|
||||
) {
|
||||
Text(
|
||||
blocker,
|
||||
modifier = Modifier.padding(12.dp),
|
||||
modifier = Modifier.padding(14.dp),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
color = MlCritical,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Firmware bundled in the APK ────────────────────────────────────────────────────
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(Modifier.padding(12.dp)) {
|
||||
Text("Firmware (bundled)", style = MaterialTheme.typography.labelLarge)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
// ── 펌웨어 정보 (APK 번들) ────────────────────────────────────────────────────────
|
||||
Card(modifier = Modifier.fillMaxWidth(), shape = cardShape, colors = cardColors) {
|
||||
Column(Modifier.padding(14.dp)) {
|
||||
Text("펌웨어 정보", style = MaterialTheme.typography.labelLarge, color = MlSecondaryText)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
val fw = firmware
|
||||
when {
|
||||
fw != null -> {
|
||||
Text(fw.assetName, style = MaterialTheme.typography.bodyMedium)
|
||||
Text(
|
||||
"${fw.imageFileName} · v${fw.version.ifEmpty { "?" }} · ${fw.sizeBytes} bytes",
|
||||
val ver = if (fw.version.isNotEmpty()) "v${fw.version}" else "버전 정보 없음"
|
||||
val sizeKb = fw.sizeBytes / 1024
|
||||
Text("$ver · ${sizeKb} KB",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold)
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(fw.imageFileName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
color = MlSecondaryText)
|
||||
}
|
||||
|
||||
firmwareError != null -> Text(
|
||||
firmwareError ?: "",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
color = MlCritical,
|
||||
)
|
||||
|
||||
else -> Text("Loading…", style = MaterialTheme.typography.bodySmall)
|
||||
else -> Text("불러오는 중…",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MlSecondaryText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Status + progress ─────────────────────────────────────────────────────────────
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(Modifier.padding(12.dp)) {
|
||||
Text(phase.name, style = MaterialTheme.typography.labelLarge)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(status, style = MaterialTheme.typography.bodyMedium)
|
||||
target?.let {
|
||||
Text(
|
||||
"${it.name} · ${it.address}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
// ── 상태 + 진행률 ─────────────────────────────────────────────────────────────────
|
||||
Card(modifier = Modifier.fillMaxWidth(), shape = cardShape, colors = cardColors) {
|
||||
Column(Modifier.padding(14.dp)) {
|
||||
Text("기기 상태", style = MaterialTheme.typography.labelLarge, color = MlSecondaryText)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
val (phaseLabel, phaseColor) = phaseKo(phase)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(10.dp)
|
||||
.background(phaseColor, shape = RoundedCornerShape(50)),
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(phaseLabel,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = phaseColor)
|
||||
}
|
||||
if (status.isNotEmpty()) {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(status, style = MaterialTheme.typography.bodySmall, color = MlSecondaryText)
|
||||
}
|
||||
target?.let {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text("기기: ${it.name}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MlSecondaryText)
|
||||
}
|
||||
if (phase == DfuPhase.UPLOADING || phase == DfuPhase.COMPLETED) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Spacer(Modifier.height(10.dp))
|
||||
LinearProgressIndicator(
|
||||
progress = { progress },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().height(8.dp),
|
||||
color = MlPrimary,
|
||||
trackColor = MlPrimary.copy(alpha = 0.15f),
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Spacer(Modifier.height(6.dp))
|
||||
val total = firmware?.sizeBytes ?: 0
|
||||
Text(
|
||||
"${(progress * 100).roundToInt()}% ($bytesSent / $total bytes)",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
val sentKb = bytesSent / 1024
|
||||
val totalKb = total / 1024
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text("${(progress * 100).roundToInt()}%",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MlPrimary)
|
||||
Text("$sentKb KB / $totalKb KB",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MlSecondaryText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Actions ───────────────────────────────────────────────────────────────────────
|
||||
// 2026-08-03: auto-connect 로 대부분 target 즉시 설정됨.
|
||||
// target 없는 경우 (fresh install · 새 기기) 에만 수동 Scan/Stop 노출.
|
||||
// ── 액션 버튼 (primary 는 gradient · secondary 는 outline) ───────────────────────
|
||||
// 2026-08-03: auto-connect 로 대부분 target 즉시 설정됨. target 없을 때만 Scan 노출.
|
||||
val hasTarget = target != null
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
if (!hasTarget || phase == DfuPhase.IDLE || phase == DfuPhase.SCANNING) {
|
||||
if (phase == DfuPhase.SCANNING) {
|
||||
OutlinedButton(onClick = { manager.stopScan() }) { Text("Stop") }
|
||||
} else {
|
||||
OutlinedButton(
|
||||
onClick = { manager.startScan() },
|
||||
enabled = !manager.isBusy,
|
||||
) { Text("Scan") }
|
||||
val outlineColors = ButtonDefaults.outlinedButtonColors(contentColor = MlPrimary)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
// 주 액션: 업데이트 시작 (조건부 gradient primary)
|
||||
if (phase != DfuPhase.UPLOADING && phase != DfuPhase.COMPLETED && phase != DfuPhase.FAILED) {
|
||||
PrimaryGradientButton(
|
||||
text = "업데이트 시작",
|
||||
onClick = { manager.startDfu() },
|
||||
enabled = manager.canStartDfu,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
// 보조 액션 (조건부)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
if (!hasTarget || phase == DfuPhase.IDLE || phase == DfuPhase.SCANNING) {
|
||||
if (phase == DfuPhase.SCANNING) {
|
||||
OutlinedButton(
|
||||
onClick = { manager.stopScan() },
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = outlineColors,
|
||||
) { Text("검색 중지") }
|
||||
} else {
|
||||
OutlinedButton(
|
||||
onClick = { manager.startScan() },
|
||||
enabled = !manager.isBusy,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = outlineColors,
|
||||
) { Text("기기 검색") }
|
||||
}
|
||||
}
|
||||
if (phase == DfuPhase.CONNECTED) {
|
||||
OutlinedButton(
|
||||
onClick = { manager.disconnect() },
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = outlineColors,
|
||||
) { Text("연결 해제") }
|
||||
}
|
||||
if (phase == DfuPhase.UPLOADING) {
|
||||
OutlinedButton(
|
||||
onClick = { manager.cancelDfu() },
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = MlCritical),
|
||||
) { Text("취소") }
|
||||
}
|
||||
if (phase == DfuPhase.COMPLETED || phase == DfuPhase.FAILED) {
|
||||
OutlinedButton(
|
||||
onClick = { manager.reset() },
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = outlineColors,
|
||||
) { Text("초기화") }
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { manager.startDfu() },
|
||||
enabled = manager.canStartDfu,
|
||||
) { Text("Start DFU") }
|
||||
|
||||
if (phase == DfuPhase.CONNECTED) {
|
||||
OutlinedButton(onClick = { manager.disconnect() }) { Text("Disconnect") }
|
||||
}
|
||||
if (phase == DfuPhase.UPLOADING) {
|
||||
OutlinedButton(onClick = { manager.cancelDfu() }) { Text("Cancel") }
|
||||
}
|
||||
if (phase == DfuPhase.COMPLETED || phase == DfuPhase.FAILED) {
|
||||
OutlinedButton(onClick = { manager.reset() }) { Text("Reset") }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Discovered devices — target 없을 때만 (auto-connect 실패 · 첫 사용) ────────
|
||||
// ── 발견된 기기 목록 (auto-connect 실패 · 첫 사용) ───────────────────────────────
|
||||
if (!hasTarget) {
|
||||
Text(
|
||||
"Devices (${manager.devices.size})",
|
||||
"발견된 기기 (${manager.devices.size})",
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MlSecondaryText,
|
||||
)
|
||||
Card(modifier = Modifier.fillMaxWidth().heightIn(min = 80.dp, max = 220.dp)) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth().heightIn(min = 80.dp, max = 220.dp),
|
||||
shape = cardShape,
|
||||
colors = cardColors,
|
||||
) {
|
||||
if (manager.devices.isEmpty()) {
|
||||
Text(
|
||||
when (phase) {
|
||||
DfuPhase.SCANNING -> "Scanning…"
|
||||
else -> "No devices yet — tap Scan."
|
||||
DfuPhase.SCANNING -> "검색 중…"
|
||||
else -> "기기가 없습니다. 검색을 눌러주세요."
|
||||
},
|
||||
modifier = Modifier.padding(12.dp),
|
||||
modifier = Modifier.padding(14.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MlSecondaryText,
|
||||
)
|
||||
} else {
|
||||
LazyColumn {
|
||||
@@ -288,15 +417,15 @@ fun DfuScreen(manager: DfuManager, modifier: Modifier = Modifier) {
|
||||
enabled = !manager.isBusy,
|
||||
onClick = { manager.connect(device) },
|
||||
)
|
||||
HorizontalDivider()
|
||||
HorizontalDivider(color = MlBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Log ───────────────────────────────────────────────────────────────────────────
|
||||
Text("Log", style = MaterialTheme.typography.labelLarge)
|
||||
// ── 상세 로그 (개발자용 · 그대로 유지) ────────────────────────────────────────────
|
||||
Text("상세 로그", style = MaterialTheme.typography.labelLarge, color = MlSecondaryText)
|
||||
LogView(
|
||||
lines = manager.logLines,
|
||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||
@@ -309,33 +438,41 @@ private fun DeviceRow(device: ScannedDevice, enabled: Boolean, onClick: () -> Un
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(device.name, style = MaterialTheme.typography.bodyMedium)
|
||||
Text(device.name, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold)
|
||||
if (device.inDfuMode) {
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.tertiaryContainer,
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = MlTeal.copy(alpha = 0.15f),
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
) {
|
||||
Text(
|
||||
"DFU",
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
"DFU 모드",
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
color = MlTeal,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
"${device.address} · ${device.rssi} dBm",
|
||||
"${device.rssi} dBm",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MlSecondaryText,
|
||||
)
|
||||
}
|
||||
Button(onClick = onClick, enabled = enabled) { Text("Connect") }
|
||||
OutlinedButton(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = MlPrimary),
|
||||
) { Text("연결", fontWeight = FontWeight.SemiBold) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,14 +484,15 @@ private fun LogView(lines: List<String>, modifier: Modifier = Modifier) {
|
||||
}
|
||||
Card(
|
||||
modifier = modifier,
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MlSecondaryText.copy(alpha = 0.08f)),
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Transparent)
|
||||
.padding(8.dp),
|
||||
.padding(10.dp),
|
||||
) {
|
||||
itemsIndexed(lines) { _, line ->
|
||||
Text(
|
||||
@@ -362,6 +500,7 @@ private fun LogView(lines: List<String>, modifier: Modifier = Modifier) {
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = MlSecondaryText,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user