Visual refresh: Material You dynamic color, dark mode, status-coloured dose cards
MedDetSammeTheme: dynamic color on Android 12+ (follows the wallpaper), green brand palette matching the launcher icon as fallback, dark mode follows the system (the app was light-only until now). Today screen: date subtitle, tonal time chips, status badges (Tatt/Venter/Utsatt/ Hoppet over) using scheme roles so contrast holds in both modes and under any dynamic palette, overdue doses get error-container cards, resolved ones fade back, FilledTonalButton for the primary action. Apotek-ærend banner in secondaryContainer; centred empty state; letter avatars and low-stock colour cues in the med/lager lists. Platform window theme switched to DayNight so the pre-Compose flash matches. Verified via emulator screenshots in light and dark mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
608d84236b
commit
6c605626c4
7 changed files with 224 additions and 23 deletions
|
|
@ -4,15 +4,15 @@ import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import no.naiv.meddetsamme.ui.AppRoot
|
import no.naiv.meddetsamme.ui.AppRoot
|
||||||
|
import no.naiv.meddetsamme.ui.MedDetSammeTheme
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
MaterialTheme {
|
MedDetSammeTheme {
|
||||||
AppRoot()
|
AppRoot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,11 +88,17 @@ fun InventoryScreen(nav: Nav) {
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Column(Modifier.weight(1f)) {
|
LetterAvatar(item.name)
|
||||||
|
Column(Modifier.padding(start = 12.dp).weight(1f)) {
|
||||||
Text("${item.name} ${item.strength}".trim(), style = MaterialTheme.typography.titleMedium)
|
Text("${item.name} ${item.strength}".trim(), style = MaterialTheme.typography.titleMedium)
|
||||||
Text(
|
Text(
|
||||||
ScheduleText.amountText(item.stockUnits, item.unit),
|
ScheduleText.amountText(item.stockUnits, item.unit),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = if (item.stockUnits <= 0.0) {
|
||||||
|
MaterialTheme.colorScheme.error
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// One-tap restock when the package size is known.
|
// One-tap restock when the package size is known.
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ package no.naiv.meddetsamme.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.DatePicker
|
import androidx.compose.material3.DatePicker
|
||||||
import androidx.compose.material3.DatePickerDialog
|
import androidx.compose.material3.DatePickerDialog
|
||||||
|
|
@ -71,6 +73,24 @@ fun guessForm(festForm: String): MedForm = when {
|
||||||
|
|
||||||
fun parseAmount(s: String): Double? = s.trim().replace(',', '.').toDoubleOrNull()
|
fun parseAmount(s: String): Double? = s.trim().replace(',', '.').toDoubleOrNull()
|
||||||
|
|
||||||
|
/** Tonal circle with the preparation's initial — cheap visual anchor in lists. */
|
||||||
|
@Composable
|
||||||
|
fun LetterAvatar(name: String) {
|
||||||
|
androidx.compose.material3.Surface(
|
||||||
|
shape = androidx.compose.foundation.shape.CircleShape,
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
) {
|
||||||
|
Box(contentAlignment = Alignment.Center) {
|
||||||
|
Text(
|
||||||
|
name.firstOrNull()?.uppercase() ?: "?",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browser link to Felleskatalogen's search for a preparation — patient-facing
|
* Browser link to Felleskatalogen's search for a preparation — patient-facing
|
||||||
* reading only. Decision #8 bans Felleskatalogen as a DATA source (licensed
|
* reading only. Decision #8 bans Felleskatalogen as a DATA source (licensed
|
||||||
|
|
|
||||||
|
|
@ -84,11 +84,13 @@ fun MedListScreen(nav: Nav) {
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Column(Modifier.weight(1f)) {
|
LetterAvatar(med.item.name)
|
||||||
|
Column(Modifier.padding(start = 12.dp).weight(1f)) {
|
||||||
Text(med.displayName, style = MaterialTheme.typography.titleMedium)
|
Text(med.displayName, style = MaterialTheme.typography.titleMedium)
|
||||||
Text(
|
Text(
|
||||||
"Lager: ${ScheduleText.amountText(med.item.stockUnits, med.item.unit)}",
|
"Lager: ${ScheduleText.amountText(med.item.stockUnits, med.item.unit)}",
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
83
app/src/main/java/no/naiv/meddetsamme/ui/Theme.kt
Normal file
83
app/src/main/java/no/naiv/meddetsamme/ui/Theme.kt
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
package no.naiv.meddetsamme.ui
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App theme: dynamic Material You colors on Android 12+ (follows the user's
|
||||||
|
* wallpaper — personal by default), with a green brand palette matching the
|
||||||
|
* launcher icon as fallback. Dark mode follows the system.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MedDetSammeTheme(content: @Composable () -> Unit) {
|
||||||
|
val dark = isSystemInDarkTheme()
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scheme = when {
|
||||||
|
Build.VERSION.SDK_INT >= 31 && dark -> dynamicDarkColorScheme(context)
|
||||||
|
Build.VERSION.SDK_INT >= 31 -> dynamicLightColorScheme(context)
|
||||||
|
dark -> DarkScheme
|
||||||
|
else -> LightScheme
|
||||||
|
}
|
||||||
|
MaterialTheme(colorScheme = scheme, content = content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Green-seeded Material 3 palette (seed ≈ launcher background #1B5E20).
|
||||||
|
private val LightScheme = lightColorScheme(
|
||||||
|
primary = Color(0xFF2E6C30),
|
||||||
|
onPrimary = Color(0xFFFFFFFF),
|
||||||
|
primaryContainer = Color(0xFFB0F1AD),
|
||||||
|
onPrimaryContainer = Color(0xFF002204),
|
||||||
|
secondary = Color(0xFF526350),
|
||||||
|
onSecondary = Color(0xFFFFFFFF),
|
||||||
|
secondaryContainer = Color(0xFFD5E8D0),
|
||||||
|
onSecondaryContainer = Color(0xFF101F10),
|
||||||
|
tertiary = Color(0xFF39656B),
|
||||||
|
onTertiary = Color(0xFFFFFFFF),
|
||||||
|
tertiaryContainer = Color(0xFFBCEBF1),
|
||||||
|
onTertiaryContainer = Color(0xFF001F23),
|
||||||
|
error = Color(0xFFBA1A1A),
|
||||||
|
onError = Color(0xFFFFFFFF),
|
||||||
|
errorContainer = Color(0xFFFFDAD6),
|
||||||
|
onErrorContainer = Color(0xFF410002),
|
||||||
|
background = Color(0xFFF7FBF1),
|
||||||
|
onBackground = Color(0xFF181D17),
|
||||||
|
surface = Color(0xFFF7FBF1),
|
||||||
|
onSurface = Color(0xFF181D17),
|
||||||
|
surfaceVariant = Color(0xFFDEE5D8),
|
||||||
|
onSurfaceVariant = Color(0xFF424940),
|
||||||
|
outline = Color(0xFF72796F),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val DarkScheme = darkColorScheme(
|
||||||
|
primary = Color(0xFF95D990),
|
||||||
|
onPrimary = Color(0xFF00390C),
|
||||||
|
primaryContainer = Color(0xFF155318),
|
||||||
|
onPrimaryContainer = Color(0xFFB0F1AD),
|
||||||
|
secondary = Color(0xFFB9CCB4),
|
||||||
|
onSecondary = Color(0xFF253423),
|
||||||
|
secondaryContainer = Color(0xFF3B4B39),
|
||||||
|
onSecondaryContainer = Color(0xFFD5E8D0),
|
||||||
|
tertiary = Color(0xFFA1CED5),
|
||||||
|
onTertiary = Color(0xFF00363C),
|
||||||
|
tertiaryContainer = Color(0xFF1F4D53),
|
||||||
|
onTertiaryContainer = Color(0xFFBCEBF1),
|
||||||
|
error = Color(0xFFFFB4AB),
|
||||||
|
onError = Color(0xFF690005),
|
||||||
|
errorContainer = Color(0xFF93000A),
|
||||||
|
onErrorContainer = Color(0xFFFFDAD6),
|
||||||
|
background = Color(0xFF101410),
|
||||||
|
onBackground = Color(0xFFE0E4DB),
|
||||||
|
surface = Color(0xFF101410),
|
||||||
|
onSurface = Color(0xFFE0E4DB),
|
||||||
|
surfaceVariant = Color(0xFF424940),
|
||||||
|
onSurfaceVariant = Color(0xFFC2C9BD),
|
||||||
|
outline = Color(0xFF8C9388),
|
||||||
|
)
|
||||||
|
|
@ -13,14 +13,17 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
|
|
@ -123,10 +126,26 @@ fun TodayScreen(nav: Nav) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val dateLine = remember {
|
||||||
|
java.time.format.DateTimeFormatter
|
||||||
|
.ofPattern("EEEE d. MMMM", java.util.Locale.forLanguageTag("nb"))
|
||||||
|
.format(LocalDate.now())
|
||||||
|
.replaceFirstChar { it.uppercase() }
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("I dag") },
|
title = {
|
||||||
|
Column {
|
||||||
|
Text("I dag")
|
||||||
|
Text(
|
||||||
|
dateLine,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = { nav.push(Screen.MedList) }) {
|
IconButton(onClick = { nav.push(Screen.MedList) }) {
|
||||||
Icon(painterResource(R.drawable.ic_meds), contentDescription = "Medisiner")
|
Icon(painterResource(R.drawable.ic_meds), contentDescription = "Medisiner")
|
||||||
|
|
@ -148,8 +167,15 @@ fun TodayScreen(nav: Nav) {
|
||||||
item { BackupNudge(onSetup = { nav.push(Screen.Settings) }) }
|
item { BackupNudge(onSetup = { nav.push(Screen.Settings) }) }
|
||||||
if (warnings.isNotEmpty()) {
|
if (warnings.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
Card(modifier = Modifier.fillMaxWidth().semantics { contentDescription = "Varsler om lager og resept" }) {
|
Card(
|
||||||
Column(Modifier.padding(12.dp)) {
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxWidth().semantics { contentDescription = "Varsler om lager og resept" },
|
||||||
|
) {
|
||||||
|
Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
Text("Apotek-ærend", style = MaterialTheme.typography.labelLarge)
|
||||||
warnings.forEach { Text(it, style = MaterialTheme.typography.bodyMedium) }
|
warnings.forEach { Text(it, style = MaterialTheme.typography.bodyMedium) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -157,11 +183,28 @@ fun TodayScreen(nav: Nav) {
|
||||||
}
|
}
|
||||||
if (doses.isEmpty()) {
|
if (doses.isEmpty()) {
|
||||||
item {
|
item {
|
||||||
Text(
|
Column(
|
||||||
"Ingen doser i dag. Legg til medisiner via pilleikonet øverst.",
|
modifier = Modifier.fillMaxWidth().padding(vertical = 64.dp),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier.padding(vertical = 24.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
)
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.ic_meds),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.outline,
|
||||||
|
modifier = Modifier.size(56.dp),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Ingen doser i dag",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Legg til medisiner via pilleikonet øverst.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.outline,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(doses, key = { "${it.doseTime.id}-${it.atMillis}" }) { dose ->
|
items(doses, key = { "${it.doseTime.id}-${it.atMillis}" }) { dose ->
|
||||||
|
|
@ -208,35 +251,61 @@ private fun DoseCard(
|
||||||
null -> null
|
null -> null
|
||||||
}
|
}
|
||||||
val resolved = dose.status == DoseStatus.TAKEN || dose.status == DoseStatus.SKIPPED
|
val resolved = dose.status == DoseStatus.TAKEN || dose.status == DoseStatus.SKIPPED
|
||||||
|
val overdue = !resolved && dose.at.isBefore(LocalDateTime.now())
|
||||||
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
|
||||||
|
// Visual state: overdue screams a little, resolved fades back, upcoming is calm.
|
||||||
|
val container = when {
|
||||||
|
overdue -> scheme.errorContainer
|
||||||
|
resolved -> scheme.surfaceContainerLow
|
||||||
|
else -> scheme.surfaceContainerHigh
|
||||||
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(containerColor = container),
|
||||||
modifier = Modifier.fillMaxWidth().semantics(mergeDescendants = true) {
|
modifier = Modifier.fillMaxWidth().semantics(mergeDescendants = true) {
|
||||||
contentDescription = buildString {
|
contentDescription = buildString {
|
||||||
append("$time, ${dose.med.displayName}, ")
|
append("$time, ${dose.med.displayName}, ")
|
||||||
append(ScheduleText.amountText(dose.doseTime.amount, dose.med.item.unit))
|
append(ScheduleText.amountText(dose.doseTime.amount, dose.med.item.unit))
|
||||||
statusText?.let { append(", status: $it") }
|
statusText?.let { append(", status: $it") }
|
||||||
|
if (overdue) append(", forfalt")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(time, style = MaterialTheme.typography.titleLarge)
|
Surface(
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
color = if (overdue) scheme.error else scheme.primaryContainer,
|
||||||
|
contentColor = if (overdue) scheme.onError else scheme.onPrimaryContainer,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
time,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
Column(Modifier.padding(start = 12.dp).weight(1f)) {
|
Column(Modifier.padding(start = 12.dp).weight(1f)) {
|
||||||
Text(dose.med.displayName, style = MaterialTheme.typography.titleMedium)
|
Text(
|
||||||
val withFood = if (dose.med.med.withFood) " — tas med mat" else ""
|
dose.med.displayName,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = if (resolved) scheme.onSurfaceVariant else scheme.onSurface,
|
||||||
|
)
|
||||||
|
val withFood = if (dose.med.med.withFood) " · tas med mat" else ""
|
||||||
Text(
|
Text(
|
||||||
ScheduleText.amountText(dose.doseTime.amount, dose.med.item.unit) + withFood,
|
ScheduleText.amountText(dose.doseTime.amount, dose.med.item.unit) + withFood,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = scheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
statusText?.let { Text(it, style = MaterialTheme.typography.labelLarge) }
|
statusText?.let { StatusBadge(it, dose.status!!) }
|
||||||
}
|
}
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
OutlinedButton(onClick = onTake) { Text("Ta nå") }
|
FilledTonalButton(onClick = onTake) { Text("Ta nå") }
|
||||||
OutlinedButton(onClick = onSkip) { Text("Hopp over") }
|
TextButton(onClick = onSkip) { Text("Hopp over") }
|
||||||
if (onSnooze != null && dose.status != null) {
|
if (onSnooze != null && dose.status != null) {
|
||||||
OutlinedButton(onClick = onSnooze) { Text("Utsett 15 min") }
|
TextButton(onClick = onSnooze) { Text("Utsett") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (onUndo != null) {
|
} else if (onUndo != null) {
|
||||||
|
|
@ -246,6 +315,24 @@ private fun DoseCard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StatusBadge(text: String, status: DoseStatus) {
|
||||||
|
val scheme = MaterialTheme.colorScheme
|
||||||
|
val (bg, fg) = when (status) {
|
||||||
|
DoseStatus.TAKEN -> scheme.primary to scheme.onPrimary
|
||||||
|
DoseStatus.SNOOZED -> scheme.tertiaryContainer to scheme.onTertiaryContainer
|
||||||
|
DoseStatus.SKIPPED -> scheme.surfaceVariant to scheme.onSurfaceVariant
|
||||||
|
DoseStatus.PENDING -> scheme.secondaryContainer to scheme.onSecondaryContainer
|
||||||
|
}
|
||||||
|
Surface(shape = MaterialTheme.shapes.small, color = bg, contentColor = fg) {
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quiet one-time card while backup is unconfigured: health data existing only
|
* Quiet one-time card while backup is unconfigured: health data existing only
|
||||||
* on this device is a real risk (proven the hard way on 2026-06-10). Goes
|
* on this device is a real risk (proven the hard way on 2026-06-10). Goes
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Compose draws everything; this only styles the window before setContent.
|
<!-- Compose draws everything; this only styles the window before setContent.
|
||||||
Platform theme, so no appcompat/material-components dependency. -->
|
DayNight platform parent so the pre-Compose flash matches dark mode. -->
|
||||||
<style name="Theme.MedDetSamme" parent="android:Theme.Material.Light.NoActionBar" />
|
<style name="Theme.MedDetSamme" parent="android:Theme.DeviceDefault.DayNight" >
|
||||||
|
<item name="android:windowActionBar">false</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue