diff --git a/app/src/main/java/com/example/medilightv2android/AppState.kt b/app/src/main/java/com/example/medilightv2android/AppState.kt index 8ecd0c6..addd447 100644 --- a/app/src/main/java/com/example/medilightv2android/AppState.kt +++ b/app/src/main/java/com/example/medilightv2android/AppState.kt @@ -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) { diff --git a/app/src/main/java/com/example/medilightv2android/MainActivity.kt b/app/src/main/java/com/example/medilightv2android/MainActivity.kt index eb39bf6..587c869 100644 --- a/app/src/main/java/com/example/medilightv2android/MainActivity.kt +++ b/app/src/main/java/com/example/medilightv2android/MainActivity.kt @@ -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 }) } } } \ No newline at end of file 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 aa5ffc7..226f284 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 @@ -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)) diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VivamyoMonitoringView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VivamyoMonitoringView.kt index 8dabcb1..115b846 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VivamyoMonitoringView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VivamyoMonitoringView.kt @@ -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) diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VoidingDiaryView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VoidingDiaryView.kt new file mode 100644 index 0000000..db49417 --- /dev/null +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/VoidingDiaryView.kt @@ -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(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("취소") } } + ) + } +}