diff --git a/app/src/main/java/com/example/medilightv2android/ui/components/DonutChartView.kt b/app/src/main/java/com/example/medilightv2android/ui/components/DonutChartView.kt index 27307ca..ea10b19 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/components/DonutChartView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/components/DonutChartView.kt @@ -38,20 +38,33 @@ fun DonutChartView( .semantics { contentDescription = "Bladder fill $pct percent, ${urgency.message}" } ) { val strokeWidth = lineWidth.toPx() + // Stroke 외곽이 Canvas 경계 밖으로 튀어나오는 걸 막기 위해 radius를 inner로 명시. + // 회의 #5: 게이지가 도넛 외곽선을 이탈해 보이던 문제 해결. + val outerR = this.size.minDimension / 2f + val innerR = outerR - strokeWidth / 2f + val center = androidx.compose.ui.geometry.Offset(this.size.width / 2f, this.size.height / 2f) // Background track drawCircle( color = Color.Gray.copy(alpha = 0.12f), + radius = innerR, + center = center, style = Stroke(width = strokeWidth) ) - // Progress arc + // Progress arc — same radius if (animatedProgress > 0f) { + val arcSize = androidx.compose.ui.geometry.Size(innerR * 2, innerR * 2) + val arcOffset = androidx.compose.ui.geometry.Offset( + center.x - innerR, center.y - innerR + ) drawArc( color = gradientColors.first(), startAngle = -90f, sweepAngle = 360f * animatedProgress, useCenter = false, + topLeft = arcOffset, + size = arcSize, style = Stroke(width = strokeWidth, cap = StrokeCap.Round) ) } diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt index c38bf20..12c943d 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt @@ -1015,9 +1015,9 @@ fun PiezoMonitoringView(appState: AppState) { val baseDonutSize = donutFitSize val donutSize = baseDonutSize * 0.95f val donutLineWidth = if (isCompact) 26.dp else 34.dp - val bladdySize = donutSize * 0.85f - // ml 폰트 키움 — 회의 피드백 "숫자가 너무 작아서 안보임" - val volFontSize = 40.sp + // 회의 #4: 캐릭터 75% 축소, mL 125% 키움 → 컴팩트하게 도넛 가운데 + val bladdySize = donutSize * 0.85f * 0.75f + val volFontSize = 50.sp val maxVolumeForDonut = appState.piezoSettings.maxVolume.toDouble() val donutProgress = if (maxVolumeForDonut > 0 && maxVolumeMl > 0) @@ -1042,7 +1042,8 @@ fun PiezoMonitoringView(appState: AppState) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, - modifier = Modifier.matchParentSize().offset(y = -(donutSize * 0.03f)) + // 회의 #4: 캐릭터+mL 그룹을 위로 더 올려 도넛 가운데 컴팩트 정렬 (이전 0.03 → 0.08) + modifier = Modifier.matchParentSize().offset(y = -(donutSize * 0.08f)) ) { // Charles 833c110: Bladdy + 3-tap dev mode toggle (HomeView 패턴 통일) Box(contentAlignment = Alignment.Center) { @@ -1067,16 +1068,16 @@ fun PiezoMonitoringView(appState: AppState) { } ) } - Spacer(modifier = Modifier.height(4.dp)) + Spacer(modifier = Modifier.height(0.dp)) // 캐릭터-mL 간격 축소 Text( // Voiding 직후엔 displayMaxVolumeMl=0이고 estimatedVolume도 0 → "0 mL"가 의미 없음 → "— mL"로 if (displayMaxVolumeMl > 0) "${"%.0f".format(displayMaxVolumeMl)} mL" else if (appState.estimatedVolume > 0) "${appState.estimatedVolume} mL" else "— mL", fontSize = volFontSize, - fontWeight = if (useScrollLayout) FontWeight.Bold else FontWeight.ExtraBold, + fontWeight = FontWeight.ExtraBold, color = urgency.accentColor, - letterSpacing = if (useScrollLayout) 0.sp else (-1).sp, + letterSpacing = (-1).sp, ) } } @@ -1087,15 +1088,20 @@ fun PiezoMonitoringView(appState: AppState) { val fillPct = if (maxVol > 0 && maxVolumeMl > 0) (maxVolumeMl / maxVol * 100).coerceAtMost(100.0) else 0.0 val levelVal = if (maxVol > 0 && maxVolumeMl > 0) ((maxVolumeMl / maxVol) * BladderLevel.MAX_LEVEL).toInt().coerceIn(0, BladderLevel.MAX_LEVEL) else 0 Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { - // Auto 윈도우 < 5일 때는 표시 안 함 + // 좌측: Current Measurement (회의 #7) val showMeasurement = if (isAutoMeasuring) autoVolumeWindow.size >= 5 && lastVolumeMl > 0 else lastVolumeMl > 0 val currentMeasurement = if (showMeasurement) "%.0f".format(lastVolumeMl) else "—" - InfoCard("Current", currentMeasurement, "mL", urgency.accentColor, Modifier.weight(1f)) - // 우측 카드: title 명시 "Session Max", 값은 maxMeasured (단위 mL). - // 이전엔 title이 "X mL / Y mL"로 들어가고 값에 % 표기되어 의미 모호했음. - // % 정보는 도넛 차트가 이미 시각화 중 → 카드는 raw 숫자만. + InfoCard("Current Measurement", currentMeasurement, "mL", urgency.accentColor, Modifier.weight(1f)) + // 우측: 측정 max / max capacity 비율 (회의 #6) — 위에 XX%, 아래 raw 분수 val maxMeasured = if (displayMaxVolumeMl > 0) "%.0f".format(displayMaxVolumeMl) else "—" - InfoCard("Session Max", maxMeasured, "mL", urgency.accentColor, Modifier.weight(1f)) + val capacityVal = appState.piezoSettings.maxVolume + MaxRatioCard( + pct = fillPct.toInt(), + measured = maxMeasured, + capacity = capacityVal, + color = urgency.accentColor, + modifier = Modifier.weight(1f) + ) } // Control buttons: Void/Catheterize / Auto / Spot @@ -2137,3 +2143,23 @@ private fun InfoCard(title: String, value: String, unit: String, color: Color, m } } } + +/** 회의 #6: 측정 max / max capacity 비율 카드. 위에 XX%, 아래에 "측정값 / capacity mL". */ +@Composable +private fun MaxRatioCard(pct: Int, measured: String, capacity: Int, color: Color, modifier: Modifier = Modifier) { + Card( + modifier = modifier.shadow(4.dp, RoundedCornerShape(14.dp), spotColor = Color.Black.copy(alpha = 0.04f)), + shape = RoundedCornerShape(14.dp), + colors = CardDefaults.cardColors(containerColor = MlCardBackground) + ) { + val fs = (LocalConfiguration.current.screenWidthDp / 360f).coerceIn(1.0f, 1.8f) + Column( + modifier = Modifier.padding(vertical = 14.dp).fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text("${pct}%", fontSize = (20 * fs).sp, fontWeight = FontWeight.Bold, color = color) + Text("$measured / $capacity mL", fontSize = (12 * fs).sp, color = MlSecondaryText) + } + } +}