diff --git a/app/src/main/java/com/mapgie/goflo/ui/components/DayLogSheet.kt b/app/src/main/java/com/mapgie/goflo/ui/components/DayLogSheet.kt index 94dcb1a..e26e766 100644 --- a/app/src/main/java/com/mapgie/goflo/ui/components/DayLogSheet.kt +++ b/app/src/main/java/com/mapgie/goflo/ui/components/DayLogSheet.kt @@ -18,6 +18,8 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ButtonDefaults import androidx.compose.material.icons.outlined.WaterDrop import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenu @@ -46,10 +48,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp +import com.mapgie.goflo.data.database.entities.Group import com.mapgie.goflo.data.database.entities.PeriodEntry import com.mapgie.goflo.data.database.entities.TrackingCategory +import com.mapgie.goflo.data.database.entities.TrackingLog import com.mapgie.goflo.data.repository.TrackingLogWithValues +import com.mapgie.goflo.ui.util.COLOR_TOKEN_INHERIT import com.mapgie.goflo.ui.util.decodeScaleLabels +import com.mapgie.goflo.ui.util.effectiveColorToken import com.mapgie.goflo.ui.util.toCategoryColor import com.mapgie.goflo.ui.util.toCategoryIcon import com.mapgie.goflo.ui.util.toCategoryOnColor @@ -67,6 +73,10 @@ fun DayLogSheet( onDismiss: () -> Unit, onEditPeriod: (Long) -> Unit, onEditTrackingLog: (categoryId: Long, logId: Long) -> Unit, + /** Deletes stored logs straight from the sheet (after confirmation). */ + onDeleteTrackingLogs: (List) -> Unit, + /** Groups, so categories that inherit their colour render in it. */ + groups: List = emptyList(), /** Opens the full log menu so one category can be picked directly. */ onLogMore: () -> Unit, /** Opens the unified day screen for this day, the standard logging surface. */ @@ -84,6 +94,31 @@ fun DayLogSheet( } var showAgainstTime by rememberSaveable { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) } + /** Logs awaiting delete confirmation; empty when no dialog is open. */ + var pendingDelete by remember { mutableStateOf>(emptyList()) } + + if (pendingDelete.isNotEmpty()) { + val name = pendingDelete.first().category?.name ?: "this" + val n = pendingDelete.size + AlertDialog( + onDismissRequest = { pendingDelete = emptyList() }, + title = { Text(if (n == 1) "Delete this entry?" else "Delete these entries?") }, + text = { Text( + if (n == 1) "The $name entry for ${date.format(headerFormat)} will be permanently removed." + else "All $n $name entries for ${date.format(headerFormat)} will be permanently removed." + ) }, + confirmButton = { + TextButton( + onClick = { + onDeleteTrackingLogs(pendingDelete.map { it.log }) + pendingDelete = emptyList() + }, + colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error), + ) { Text("Delete") } + }, + dismissButton = { TextButton(onClick = { pendingDelete = emptyList() }) { Text("Cancel") } }, + ) + } ModalBottomSheet( onDismissRequest = onDismiss, @@ -139,12 +174,23 @@ fun DayLogSheet( HorizontalDivider() - // Split tracked categories into those logged with the period (the - // ones pinned into the day screen's flow context) and everything else. + // Split tracked categories into those that belong with the period + // (flow and symptoms, plus the ones pinned into the day screen's + // period context) and everything else. Flow leads. val periodLinkedCats = if (period != null) { - categoryOrder.filter { catId -> - logsByCategory[catId]?.firstOrNull()?.category?.showInLogPeriod == true - } + categoryOrder + .filter { catId -> + val cat = logsByCategory[catId]?.firstOrNull()?.category + cat != null && (cat.isSystem || cat.showInLogPeriod) + } + .sortedBy { catId -> + val cat = logsByCategory[catId]?.firstOrNull()?.category + when { + cat?.systemKey == "flow" -> 0 + cat?.isSystem == true -> 1 + else -> 2 + } + } } else { emptyList() } @@ -186,8 +232,10 @@ fun DayLogSheet( ) CategoryLogEntry( entries = entries, + groups = groups, showAgainstTime = showAgainstTime, - onEditTrackingLog = onEditTrackingLog + onEditTrackingLog = onEditTrackingLog, + onDelete = { pendingDelete = it }, ) } } @@ -207,8 +255,10 @@ fun DayLogSheet( val entries = logsByCategory[catId] ?: return@forEach CategoryLogEntry( entries = entries, + groups = groups, showAgainstTime = showAgainstTime, - onEditTrackingLog = onEditTrackingLog + onEditTrackingLog = onEditTrackingLog, + onDelete = { pendingDelete = it }, ) } @@ -243,15 +293,22 @@ fun DayLogSheet( @Composable private fun CategoryLogEntry( entries: List, + groups: List, showAgainstTime: Boolean, onEditTrackingLog: (categoryId: Long, logId: Long) -> Unit, + /** Asks to delete the given logs (one timed entry, or the whole row). */ + onDelete: (List) -> Unit, ) { val first = entries.first() val category = first.category - val bubbleColor = category?.colorToken?.toCategoryColor() - ?: MaterialTheme.colorScheme.secondary - val onBubble = category?.colorToken?.toCategoryOnColor() - ?: MaterialTheme.colorScheme.onSecondary + // The group's colour when the category inherits it; a category with no + // colour of its own and no group is neutral, and its value text must + // then read as ordinary text rather than vanish into the surface tint. + val token = category?.effectiveColorToken(groups) + val bubbleColor = token?.toCategoryColor() ?: MaterialTheme.colorScheme.secondary + val onBubble = token?.toCategoryOnColor() ?: MaterialTheme.colorScheme.onSecondary + val valueColor = if (token == null || token == COLOR_TOKEN_INHERIT) + MaterialTheme.colorScheme.onSurface else bubbleColor val icon = category?.iconName?.toCategoryIcon()?.vector val hasTimedEntries = showAgainstTime && @@ -263,7 +320,8 @@ private fun CategoryLogEntry( iconColor = bubbleColor, iconOnColor = onBubble, label = category?.name ?: "Unknown", - onEdit = { onEditTrackingLog(first.log.categoryId, entries.last().log.id) } + onEdit = { onEditTrackingLog(first.log.categoryId, entries.last().log.id) }, + onDelete = { onDelete(entries) }, ) { if (hasTimedEntries) { // Show each entry with its timestamp on its own line @@ -297,6 +355,16 @@ private fun CategoryLogEntry( color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) ) } + TextButton( + onClick = { onDelete(listOf(entry)) }, + contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp) + ) { + Text( + text = "delete", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) + ) + } } } } else { @@ -314,7 +382,7 @@ private fun CategoryLogEntry( Text( text = allDisplayValues[0], style = MaterialTheme.typography.titleMedium, - color = bubbleColor + color = valueColor ) } else { Text( @@ -357,7 +425,8 @@ private fun LogEntryRow( iconOnColor: Color, label: String, onEdit: () -> Unit, - content: @Composable ColumnScope.() -> Unit + onDelete: (() -> Unit)? = null, + content: @Composable ColumnScope.() -> Unit, ) { Row( modifier = Modifier.fillMaxWidth(), @@ -404,6 +473,18 @@ private fun LogEntryRow( color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) ) } + if (onDelete != null) { + TextButton( + onClick = onDelete, + contentPadding = PaddingValues(horizontal = 4.dp, vertical = 0.dp) + ) { + Text( + text = "delete", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) + ) + } + } } } diff --git a/app/src/main/java/com/mapgie/goflo/ui/screens/home/HomeScreen.kt b/app/src/main/java/com/mapgie/goflo/ui/screens/home/HomeScreen.kt index 814ba99..316ed02 100644 --- a/app/src/main/java/com/mapgie/goflo/ui/screens/home/HomeScreen.kt +++ b/app/src/main/java/com/mapgie/goflo/ui/screens/home/HomeScreen.kt @@ -143,7 +143,9 @@ fun HomeScreen( date = data.date, period = data.period, trackingLogs = data.trackingLogs, + groups = data.groups, onDismiss = { viewModel.clearSelectedDay() }, + onDeleteTrackingLogs = { viewModel.deleteTrackingLogs(it) }, onEditPeriod = { viewModel.clearSelectedDay() // The unified day screen edits this specific day's own flow diff --git a/app/src/main/java/com/mapgie/goflo/ui/screens/home/HomeViewModel.kt b/app/src/main/java/com/mapgie/goflo/ui/screens/home/HomeViewModel.kt index 78c8ccd..1beb629 100644 --- a/app/src/main/java/com/mapgie/goflo/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/java/com/mapgie/goflo/ui/screens/home/HomeViewModel.kt @@ -3,8 +3,10 @@ package com.mapgie.goflo.ui.screens.home import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import com.mapgie.goflo.data.database.entities.Group import com.mapgie.goflo.data.database.entities.PeriodEntry import com.mapgie.goflo.data.database.entities.TrackingCategory +import com.mapgie.goflo.data.database.entities.TrackingLog import com.mapgie.goflo.data.preferences.AppPreferencesStore import com.mapgie.goflo.data.repository.PeriodRepository import com.mapgie.goflo.data.repository.TrackingLogWithValues @@ -70,6 +72,8 @@ data class DayLogData( val date: LocalDate, val period: PeriodEntry?, val trackingLogs: List, + /** Groups, so categories that inherit their colour render in it. */ + val groups: List = emptyList(), ) @OptIn(ExperimentalCoroutinesApi::class) @@ -171,8 +175,9 @@ class HomeViewModel( combine( repository.getAllPeriods(), - trackingRepository.getLogsForDate(date) - ) { periods, trackingLogs -> + trackingRepository.getLogsForDate(date), + trackingRepository.getAllGroups(), + ) { periods, trackingLogs, groups -> val period = periods.firstOrNull { p -> val start = LocalDate.parse(p.startDate) val end = p.endDate?.let { LocalDate.parse(it) } ?: LocalDate.now() @@ -182,6 +187,7 @@ class HomeViewModel( date = date, period = period, trackingLogs = trackingLogs, + groups = groups, ) } } @@ -190,6 +196,16 @@ class HomeViewModel( fun selectDay(date: LocalDate) { _selectedDay.value = date } fun clearSelectedDay() { _selectedDay.value = null } + /** + * Deletes stored tracking logs from the day sheet. The sheet's data is a + * live query, so it refreshes on its own. + */ + fun deleteTrackingLogs(logs: List) { + viewModelScope.launch { + logs.forEach { trackingRepository.deleteLog(it) } + } + } + // ── Quick increment (Plus One categories) ─────────────────────────────────── /** Transient confirmation message after an instant increment; null when none pending. */ diff --git a/changelog/unreleased/day-sheet-delete-and-colours.json b/changelog/unreleased/day-sheet-delete-and-colours.json new file mode 100644 index 0000000..c39b67a --- /dev/null +++ b/changelog/unreleased/day-sheet-delete-and-colours.json @@ -0,0 +1,6 @@ +{ + "bump": "patch", + "fixed": [ + "The calendar day sheet now lets you delete any entry (including Flow and Symptoms) with a confirmation, shows Flow and Symptoms alongside the period instead of under \"Tracked\", and renders categories that inherit their group's colour in that colour so their values are readable" + ] +}