Add voiding diary view + home icon navigation
VoidingDiaryView: - Date-grouped record list (grouped by day) - Shows volume/awake/sleep with icons and colors - Method badge (수기/음성/카메라) - Delete with confirmation dialog - Total record count footer Navigation changes: - Piezo screen: ArrowBack → Home icon (집 아이콘) - Piezo screen: 배뇨일지 icon (Description) added next to home - NIRS screen: ArrowBack → Home icon - AppScreen.VOIDING_DIARY route added to Crossfade navigation - VoidingDiaryView navigates back to HOME Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,8 @@ import com.example.medilightv2android.services.*
|
||||
enum class AppScreen {
|
||||
ONBOARDING, REGISTRATION, HOME, SENSOR_SELECT, DEVICE_SCAN,
|
||||
NIRS_CALIBRATION, PIEZO_PERSONALIZATION, PIN_SETUP, PIN_ENTRY,
|
||||
VIVAMYO_MONITORING, PIEZO_MONITORING, MEASUREMENT_HISTORY
|
||||
VIVAMYO_MONITORING, PIEZO_MONITORING, MEASUREMENT_HISTORY,
|
||||
VOIDING_DIARY
|
||||
}
|
||||
|
||||
class AppState(private val context: Context) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.example.medilightv2android.ui.views.history.MeasurementHistoryView
|
||||
import com.example.medilightv2android.ui.views.home.HomeView
|
||||
import com.example.medilightv2android.ui.views.monitoring.PiezoMonitoringView
|
||||
import com.example.medilightv2android.ui.views.monitoring.VivamyoMonitoringView
|
||||
import com.example.medilightv2android.ui.views.monitoring.VoidingDiaryView
|
||||
import com.example.medilightv2android.ui.views.onboarding.OnboardingView
|
||||
import com.example.medilightv2android.ui.views.personalization.PiezoPersonalizationView
|
||||
import com.example.medilightv2android.ui.views.pin.PinMode
|
||||
@@ -74,6 +75,7 @@ fun MedilightApp(modifier: Modifier = Modifier) {
|
||||
AppScreen.VIVAMYO_MONITORING -> VivamyoMonitoringView(appState)
|
||||
AppScreen.PIEZO_MONITORING -> PiezoMonitoringView(appState)
|
||||
AppScreen.MEASUREMENT_HISTORY -> MeasurementHistoryView(appState)
|
||||
AppScreen.VOIDING_DIARY -> VoidingDiaryView(onBack = { appState.currentScreen = AppScreen.HOME })
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -2,6 +2,7 @@ package com.example.medilightv2android.ui.views.monitoring
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.example.medilightv2android.AppScreen
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
@@ -170,7 +171,10 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(onClick = { appState.goHome() }, modifier = Modifier.size(if (isCompact) 36.dp else 48.dp)) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
Icon(Icons.Default.Home, "Home", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
}
|
||||
IconButton(onClick = { appState.currentScreen = AppScreen.VOIDING_DIARY }, modifier = Modifier.size(if (isCompact) 36.dp else 48.dp)) {
|
||||
Icon(Icons.Default.Description, "배뇨일지", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
}
|
||||
if (!isCompact) {
|
||||
Text("Bladder Monitor", fontSize = 20.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp))
|
||||
|
||||
+1
-1
@@ -240,7 +240,7 @@ fun VivamyoMonitoringView(appState: AppState) {
|
||||
nirsManager.reset()
|
||||
appState.goHome()
|
||||
}, modifier = Modifier.size(if (isCompact) 36.dp else 48.dp)) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
Icon(Icons.Default.Home, "Home", tint = MlPrimary, modifier = Modifier.size(if (isCompact) 20.dp else 24.dp))
|
||||
}
|
||||
Column(modifier = Modifier.padding(start = if (isCompact) 4.dp else 8.dp)) {
|
||||
Text("Vivamyo", fontSize = if (isCompact) 16.sp else 20.sp, fontWeight = FontWeight.Bold)
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package com.example.medilightv2android.ui.views.monitoring
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.medilightv2android.measure.VoidingRecord
|
||||
import com.example.medilightv2android.measure.VoidingRecordStore
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 배뇨일지 목록 화면 — 로컬 기록 조회
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun VoidingDiaryView(onBack: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
var records by remember { mutableStateOf(VoidingRecordStore.getAll(context).sortedByDescending { it.timestamp }) }
|
||||
var showDeleteConfirm by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
val grouped = remember(records) {
|
||||
records.groupBy { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it.timestamp)) }
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("배뇨일지", fontWeight = FontWeight.SemiBold) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) { Icon(Icons.Default.ArrowBack, "뒤로") }
|
||||
}
|
||||
)
|
||||
}
|
||||
) { padding ->
|
||||
if (records.isEmpty()) {
|
||||
Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(Icons.Default.Description, null, Modifier.size(48.dp), Color.Gray.copy(alpha = 0.5f))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("기록이 없습니다", color = Color.Gray)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
Modifier.fillMaxSize().padding(padding),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
grouped.forEach { (date, dayRecords) ->
|
||||
item {
|
||||
val displayDate = try {
|
||||
val d = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)
|
||||
SimpleDateFormat("M월 d일 (E)", Locale.KOREA).format(d!!)
|
||||
} catch (_: Exception) { date }
|
||||
|
||||
Text(displayDate, fontWeight = FontWeight.Bold, fontSize = 15.sp,
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp))
|
||||
}
|
||||
|
||||
items(dayRecords, key = { it.id }) { record ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.background(Color.Gray.copy(alpha = 0.08f), RoundedCornerShape(8.dp))
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(record.timeString, fontFamily = FontFamily.Monospace, color = Color.Gray,
|
||||
modifier = Modifier.width(50.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
|
||||
when {
|
||||
record.volume != null -> {
|
||||
Icon(Icons.Default.WaterDrop, null, Modifier.size(16.dp), Color(0xFF007AFF))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("${record.volume} ml", fontWeight = FontWeight.SemiBold)
|
||||
}
|
||||
record.activity == "awake" -> {
|
||||
Icon(Icons.Default.WbSunny, null, Modifier.size(16.dp), Color(0xFFFF9800))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("기상")
|
||||
}
|
||||
record.activity == "sleep" -> {
|
||||
Icon(Icons.Default.DarkMode, null, Modifier.size(16.dp), Color(0xFF9C27B0))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("취침")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
// 입력 방식 뱃지
|
||||
val methodLabel = when (record.method) {
|
||||
"voice" -> "음성"
|
||||
"camera" -> "카메라"
|
||||
else -> "수기"
|
||||
}
|
||||
Text(methodLabel, fontSize = 10.sp, color = Color.Gray,
|
||||
modifier = Modifier.background(Color.Gray.copy(alpha = 0.15f), RoundedCornerShape(4.dp))
|
||||
.padding(horizontal = 6.dp, vertical = 2.dp))
|
||||
|
||||
Spacer(Modifier.width(8.dp))
|
||||
|
||||
// 삭제
|
||||
IconButton(onClick = { showDeleteConfirm = record.id }, modifier = Modifier.size(24.dp)) {
|
||||
Icon(Icons.Default.Delete, "삭제", Modifier.size(16.dp), Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text("총 ${records.size}건", color = Color.Gray, fontSize = 12.sp,
|
||||
modifier = Modifier.fillMaxWidth(), textAlign = androidx.compose.ui.text.style.TextAlign.Center)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 삭제 확인
|
||||
showDeleteConfirm?.let { id ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteConfirm = null },
|
||||
title = { Text("기록 삭제") },
|
||||
text = { Text("이 기록을 삭제하시겠습니까?") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
VoidingRecordStore.delete(context, id)
|
||||
records = VoidingRecordStore.getAll(context).sortedByDescending { it.timestamp }
|
||||
showDeleteConfirm = null
|
||||
}) { Text("삭제", color = Color.Red) }
|
||||
},
|
||||
dismissButton = { TextButton(onClick = { showDeleteConfirm = null }) { Text("취소") } }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user