Typography: Manrope headings, Inter body — plus norsk dag/dager pluralization

Manrope (variable wght, instanced at 600/700/800) for display/headline/
title roles; Inter static Regular/Medium/SemiBold for body/label. M3
baseline metrics retained — only family and weight change, so component
layouts and accessibility text scaling are untouched. Both fonts are
OFL (THIRD_PARTY_LICENSES.md added) with full æøå/µ coverage, verified
by emulator screenshot. ScheduleText.daysCount fixes 'lager for 1
dager' spotted in that screenshot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-06-11 17:09:07 +02:00
commit 9b99f2cf62
9 changed files with 63 additions and 3 deletions

13
THIRD_PARTY_LICENSES.md Normal file
View file

@ -0,0 +1,13 @@
# Third-party licenses
## Fonts (bundled in `app/src/main/res/font/`)
- **Inter** (`inter_*.ttf`) — © The Inter Project Authors,
<https://github.com/rsms/inter>. Licensed under the SIL Open Font
License 1.1 (<https://openfontlicense.org>).
- **Manrope** (`manrope_variable.ttf`) — © The Manrope Project Authors,
distributed via <https://github.com/google/fonts/tree/main/ofl/manrope>.
Licensed under the SIL Open Font License 1.1.
Library licenses (Apache-2.0 and similar) are declared by their respective
artifacts via Gradle metadata.

View file

@ -50,7 +50,7 @@ class SupplyCheckReceiver : BroadcastReceiver() {
for (med in meds) rate += ScheduleEngine.dailyConsumption(db.doseTimeDao().getForMed(med.med.id))
if (ScheduleEngine.needsRefill(item.stockUnits, rate, item.lowStockLeadDays)) {
val days = ScheduleEngine.daysOfSupply(item.stockUnits, rate)
lowStock += "${item.name}: lager for ${days?.toInt() ?: 0} dager"
lowStock += "${item.name}: lager for ${no.naiv.meddetsamme.domain.ScheduleText.daysCount(days?.toInt() ?: 0)}"
}
if (ScheduleEngine.needsRenewal(item.rxExpiryEpochDay, today, RX_LEAD_DAYS)) {
val expired = item.rxExpiryEpochDay!! < today.toEpochDay()

View file

@ -36,6 +36,9 @@ object ScheduleText {
}
}
/** "1 dag", "3 dager" — norsk flertall. */
fun daysCount(days: Int): String = if (days == 1) "1 dag" else "$days dager"
/** "1 tablett", "2,5 ml" — norsk desimalkomma. */
fun amountText(amount: Double, unit: String): String {
val number = if (amount == amount.toLong().toDouble()) {

View file

@ -1,8 +1,11 @@
@file:OptIn(androidx.compose.ui.text.ExperimentalTextApi::class)
package no.naiv.meddetsamme.ui
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
@ -10,6 +13,11 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.text.font.FontWeight
import no.naiv.meddetsamme.R
/**
* App theme: dynamic Material You colors on Android 12+ (follows the user's
@ -26,7 +34,43 @@ fun MedDetSammeTheme(content: @Composable () -> Unit) {
dark -> DarkScheme
else -> LightScheme
}
MaterialTheme(colorScheme = scheme, content = content)
MaterialTheme(colorScheme = scheme, typography = AppTypography, content = content)
}
// Manrope for headings/titles (friendly, confident), Inter for body/labels
// (built for UI legibility at small sizes). Both OFL, full æøå/µ coverage.
private val Manrope = FontFamily(
Font(R.font.manrope_variable, FontWeight.SemiBold, variationSettings = FontVariation.Settings(FontVariation.weight(600))),
Font(R.font.manrope_variable, FontWeight.Bold, variationSettings = FontVariation.Settings(FontVariation.weight(700))),
Font(R.font.manrope_variable, FontWeight.ExtraBold, variationSettings = FontVariation.Settings(FontVariation.weight(800))),
)
private val Inter = FontFamily(
Font(R.font.inter_regular, FontWeight.Normal),
Font(R.font.inter_medium, FontWeight.Medium),
Font(R.font.inter_semibold, FontWeight.SemiBold),
)
// M3 baseline metrics, only family/weight swapped — sizes and line heights
// are already tuned for the components.
private val AppTypography: Typography = Typography().let { base ->
Typography(
displayLarge = base.displayLarge.copy(fontFamily = Manrope, fontWeight = FontWeight.ExtraBold),
displayMedium = base.displayMedium.copy(fontFamily = Manrope, fontWeight = FontWeight.ExtraBold),
displaySmall = base.displaySmall.copy(fontFamily = Manrope, fontWeight = FontWeight.Bold),
headlineLarge = base.headlineLarge.copy(fontFamily = Manrope, fontWeight = FontWeight.Bold),
headlineMedium = base.headlineMedium.copy(fontFamily = Manrope, fontWeight = FontWeight.Bold),
headlineSmall = base.headlineSmall.copy(fontFamily = Manrope, fontWeight = FontWeight.Bold),
titleLarge = base.titleLarge.copy(fontFamily = Manrope, fontWeight = FontWeight.Bold),
titleMedium = base.titleMedium.copy(fontFamily = Manrope, fontWeight = FontWeight.SemiBold),
titleSmall = base.titleSmall.copy(fontFamily = Manrope, fontWeight = FontWeight.SemiBold),
bodyLarge = base.bodyLarge.copy(fontFamily = Inter),
bodyMedium = base.bodyMedium.copy(fontFamily = Inter),
bodySmall = base.bodySmall.copy(fontFamily = Inter),
labelLarge = base.labelLarge.copy(fontFamily = Inter, fontWeight = FontWeight.Medium),
labelMedium = base.labelMedium.copy(fontFamily = Inter, fontWeight = FontWeight.Medium),
labelSmall = base.labelSmall.copy(fontFamily = Inter, fontWeight = FontWeight.Medium),
)
}
// Green-seeded Material 3 palette (seed ≈ launcher background #1B5E20).

View file

@ -115,7 +115,7 @@ fun TodayScreen(nav: Nav) {
val rate = ratePerItem[item.id] ?: 0.0
if (ScheduleEngine.needsRefill(item.stockUnits, rate, item.lowStockLeadDays)) {
val days = ScheduleEngine.daysOfSupply(item.stockUnits, rate)?.toInt() ?: 0
warn += "${item.name}: lager for $days dager"
warn += "${item.name}: lager for ${ScheduleText.daysCount(days)}"
}
if (ScheduleEngine.needsRenewal(item.rxExpiryEpochDay, today)) {
warn += "${item.name}: resept må fornyes"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.