History: leading green check / red cross, and sane status labels
Each history row now starts with a green check (taken) or red cross (not taken), so the day scans at a glance. The split mirrors adherencePercent — only TAKEN counts as adhered — so the icons agree with the percentages on top. Also fixes the nonsense label: a past PENDING dose showed "Venter" as if it were still due. A history-local historyLabel maps PENDING -> "Ikke tatt"; the shared DoseStatus.label is untouched, so Today still shows "Venter". The fixed takenGreen/missedRed are deliberately not derived from the palette, or dynamic Material You colours could tint a "taken" tick non-green. Colour is paired with shape (check vs cross) and text, so meaning never rests on colour alone (WCAG 1.4.1). The trailing tonal DoseStatusBadge gave way to a muted text label, since the leading icon now carries the colour signal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7eac153740
commit
bbf4d0af9c
4 changed files with 57 additions and 2 deletions
|
|
@ -37,11 +37,21 @@ import java.time.format.DateTimeFormatter
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import no.naiv.meddetsamme.R
|
import no.naiv.meddetsamme.R
|
||||||
import no.naiv.meddetsamme.data.DoseLog
|
import no.naiv.meddetsamme.data.DoseLog
|
||||||
|
import no.naiv.meddetsamme.data.DoseStatus
|
||||||
import no.naiv.meddetsamme.data.MedDatabase
|
import no.naiv.meddetsamme.data.MedDatabase
|
||||||
import no.naiv.meddetsamme.domain.ScheduleEngine
|
import no.naiv.meddetsamme.domain.ScheduleEngine
|
||||||
|
|
||||||
private const val HISTORY_DAYS = 30L
|
private const val HISTORY_DAYS = 30L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status text in a *past* context. Differs from [label] only for PENDING: on
|
||||||
|
* the Today screen a due dose is legitimately "Venter", but a dose from days
|
||||||
|
* ago that was never acted on was simply not taken — "Venter" there is the
|
||||||
|
* nonsense this screen used to show.
|
||||||
|
*/
|
||||||
|
private val DoseStatus.historyLabel: String
|
||||||
|
get() = if (this == DoseStatus.PENDING) "Ikke tatt" else label
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adherence history: the last 30 days of dose logs, newest first, with
|
* Adherence history: the last 30 days of dose logs, newest first, with
|
||||||
* 7- and 30-day adherence percentages on top. Read-only — corrections happen
|
* 7- and 30-day adherence percentages on top. Read-only — corrections happen
|
||||||
|
|
@ -130,22 +140,36 @@ fun HistoryScreen(nav: Nav) {
|
||||||
}
|
}
|
||||||
items(dayLogs, key = { it.first.id }) { (log, medName) ->
|
items(dayLogs, key = { it.first.id }) { (log, medName) ->
|
||||||
val time = timeFmt.format(Instant.ofEpochMilli(log.scheduledAtMillis).atZone(zone))
|
val time = timeFmt.format(Instant.ofEpochMilli(log.scheduledAtMillis).atZone(zone))
|
||||||
|
val taken = log.status == DoseStatus.TAKEN
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth().semantics(mergeDescendants = true) {
|
modifier = Modifier.fillMaxWidth().semantics(mergeDescendants = true) {
|
||||||
contentDescription = "$time, $medName, ${log.status.label}"
|
contentDescription = "$time, $medName, ${log.status.historyLabel}"
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
|
Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
// Green ✓ taken / red ✕ not-taken, first on the line for an
|
||||||
|
// at-a-glance scan down the day. Decorative here — the row's
|
||||||
|
// merged contentDescription already speaks the status.
|
||||||
|
Icon(
|
||||||
|
painterResource(if (taken) R.drawable.ic_check else R.drawable.ic_close),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (taken) takenGreen() else missedRed(),
|
||||||
|
modifier = Modifier.padding(end = 12.dp),
|
||||||
|
)
|
||||||
Text(time, style = MaterialTheme.typography.titleSmall)
|
Text(time, style = MaterialTheme.typography.titleSmall)
|
||||||
Text(
|
Text(
|
||||||
medName,
|
medName,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
modifier = Modifier.padding(start = 12.dp).weight(1f),
|
modifier = Modifier.padding(start = 12.dp).weight(1f),
|
||||||
)
|
)
|
||||||
DoseStatusBadge(log.status)
|
Text(
|
||||||
|
log.status.historyLabel,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,19 @@ fun MedDetSammeTheme(content: @Composable () -> Unit) {
|
||||||
MaterialTheme(colorScheme = scheme, typography = AppTypography, content = content)
|
MaterialTheme(colorScheme = scheme, typography = AppTypography, content = content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixed adherence semantics for the history glance icons: green = taken,
|
||||||
|
* red = not taken. Deliberately *not* derived from the (possibly dynamic)
|
||||||
|
* brand palette — a wallpaper-blue "primary" must never make a "taken" tick
|
||||||
|
* read as anything but green. Paired with a distinct shape (✓ vs ✕) and text
|
||||||
|
* so meaning never rests on colour alone (WCAG 1.4.1).
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun takenGreen(): Color = if (isSystemInDarkTheme()) Color(0xFF7CC97F) else Color(0xFF2E7D32)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun missedRed(): Color = if (isSystemInDarkTheme()) Color(0xFFFFB4AB) else Color(0xFFBA1A1A)
|
||||||
|
|
||||||
// Manrope for headings/titles (friendly, confident), Inter for body/labels
|
// Manrope for headings/titles (friendly, confident), Inter for body/labels
|
||||||
// (built for UI legibility at small sizes). Both OFL, full æøå/µ coverage.
|
// (built for UI legibility at small sizes). Both OFL, full æøå/µ coverage.
|
||||||
private val Manrope = FontFamily(
|
private val Manrope = FontFamily(
|
||||||
|
|
|
||||||
9
app/src/main/res/drawable/ic_check.xml
Normal file
9
app/src/main/res/drawable/ic_check.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp" android:height="24dp"
|
||||||
|
android:viewportWidth="24" android:viewportHeight="24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/ic_close.xml
Normal file
9
app/src/main/res/drawable/ic_close.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp" android:height="24dp"
|
||||||
|
android:viewportWidth="24" android:viewportHeight="24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||||
|
</vector>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue