From 9dad26e5bde7e8c01a2d660ffca2d5a992ebc51f Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 17 Oct 2023 13:18:37 +0200 Subject: [PATCH 1/3] Add `Modifier.withBaseline()` --- .../common/baseline/BaselineModifier.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/baseline/BaselineModifier.kt diff --git a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/baseline/BaselineModifier.kt b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/baseline/BaselineModifier.kt new file mode 100644 index 00000000000..c6a1cd8a44b --- /dev/null +++ b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/baseline/BaselineModifier.kt @@ -0,0 +1,31 @@ +package app.k9mail.core.ui.compose.common.baseline + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.FirstBaseline +import androidx.compose.ui.layout.LastBaseline +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Dp + +/** + * Adds a baseline to a Composable that typically doesn't have one. + * + * This can be used to align e.g. an icon to the baseline of some text next to it. See e.g. [RowScope.alignByBaseline]. + * + * @param baseline The number of device-independent pixels (dp) the baseline is from the top of the Composable. + */ +fun Modifier.withBaseline(baseline: Dp) = layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + val baselineInPx = baseline.roundToPx() + + layout( + width = placeable.width, + height = placeable.height, + alignmentLines = mapOf( + FirstBaseline to baselineInPx, + LastBaseline to baselineInPx, + ), + ) { + placeable.placeRelative(x = 0, y = 0) + } +} From 34bbf83c909049d9da5156f1ee71395ba35a44c9 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 17 Oct 2023 13:33:02 +0200 Subject: [PATCH 2/3] Add `IconsWithBaseline` --- .../ui/compose/common/image/ImageWithBaseline.kt | 11 +++++++++++ .../core/ui/compose/theme/IconsWithBaseline.kt | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/image/ImageWithBaseline.kt create mode 100644 core/ui/compose/theme/src/main/java/app/k9mail/core/ui/compose/theme/IconsWithBaseline.kt diff --git a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/image/ImageWithBaseline.kt b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/image/ImageWithBaseline.kt new file mode 100644 index 00000000000..123ed6f3955 --- /dev/null +++ b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/image/ImageWithBaseline.kt @@ -0,0 +1,11 @@ +package app.k9mail.core.ui.compose.common.image + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.Dp + +@Immutable +data class ImageWithBaseline( + val image: ImageVector, + val baseline: Dp, +) diff --git a/core/ui/compose/theme/src/main/java/app/k9mail/core/ui/compose/theme/IconsWithBaseline.kt b/core/ui/compose/theme/src/main/java/app/k9mail/core/ui/compose/theme/IconsWithBaseline.kt new file mode 100644 index 00000000000..ebaf7667045 --- /dev/null +++ b/core/ui/compose/theme/src/main/java/app/k9mail/core/ui/compose/theme/IconsWithBaseline.kt @@ -0,0 +1,16 @@ +package app.k9mail.core.ui.compose.theme + +import androidx.compose.material.icons.filled.Warning +import androidx.compose.ui.unit.dp +import app.k9mail.core.ui.compose.common.image.ImageWithBaseline +import androidx.compose.material.icons.Icons as MaterialIcons + +// We're using "by lazy" so not all icons are loaded into memory as soon as a nested object is accessed. But once a +// property is accessed we want to retain the `ImageWithBaseline` instance. +object IconsWithBaseline { + object Filled { + val warning: ImageWithBaseline by lazy { + ImageWithBaseline(image = MaterialIcons.Filled.Warning, baseline = 21.dp) + } + } +} From d8a85555546b11abf7935b05508577862c8b7073 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 17 Oct 2023 14:19:30 +0200 Subject: [PATCH 3/3] Add warning icon to server certificate error screen --- .../ui/ServerCertificateErrorScreen.kt | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/ui/ServerCertificateErrorScreen.kt b/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/ui/ServerCertificateErrorScreen.kt index bcf6bc6336f..295152000d8 100644 --- a/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/ui/ServerCertificateErrorScreen.kt +++ b/feature/account/server/certificate/src/main/kotlin/app/k9mail/feature/account/server/certificate/ui/ServerCertificateErrorScreen.kt @@ -2,16 +2,20 @@ package app.k9mail.feature.account.server.certificate.ui import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import app.k9mail.core.ui.compose.common.DevicePreviews +import app.k9mail.core.ui.compose.common.baseline.withBaseline import app.k9mail.core.ui.compose.common.mvi.observe +import app.k9mail.core.ui.compose.designsystem.atom.Icon import app.k9mail.core.ui.compose.designsystem.atom.button.Button import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonOutlined import app.k9mail.core.ui.compose.designsystem.atom.text.TextBody1 @@ -19,6 +23,7 @@ import app.k9mail.core.ui.compose.designsystem.atom.text.TextHeadline4 import app.k9mail.core.ui.compose.designsystem.atom.text.TextSubtitle1 import app.k9mail.core.ui.compose.designsystem.template.ResponsiveWidthContainer import app.k9mail.core.ui.compose.designsystem.template.Scaffold +import app.k9mail.core.ui.compose.theme.IconsWithBaseline import app.k9mail.core.ui.compose.theme.K9Theme import app.k9mail.core.ui.compose.theme.MainTheme import app.k9mail.feature.account.server.certificate.data.InMemoryServerCertificateErrorRepository @@ -32,6 +37,7 @@ import org.koin.androidx.compose.koinViewModel // Note: This is a placeholder with mostly hardcoded text. // TODO: Replace with final design. +@Suppress("LongMethod") @Composable fun ServerCertificateErrorScreen( onCertificateAccepted: () -> Unit, @@ -79,7 +85,27 @@ fun ServerCertificateErrorScreen( .verticalScroll(rememberScrollState()) .padding(all = MainTheme.spacings.double), ) { - TextHeadline4(text = "Warning") + Row { + val warningIcon = IconsWithBaseline.Filled.warning + val iconSize = MainTheme.sizes.medium + val iconScalingFactor = iconSize / warningIcon.image.defaultHeight + val iconBaseline = warningIcon.baseline * iconScalingFactor + + Icon( + imageVector = warningIcon.image, + tint = MainTheme.colors.warning, + modifier = Modifier + .padding(end = MainTheme.spacings.default) + .requiredSize(iconSize) + .withBaseline(iconBaseline) + .alignByBaseline(), + ) + TextHeadline4( + text = "Warning", + modifier = Modifier.alignByBaseline(), + ) + } + TextSubtitle1("Invalid certificate") Spacer(modifier = Modifier.height(MainTheme.spacings.quadruple))