diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/MainActivity.kt b/app/src/main/kotlin/io/flamenet/messenger/app/MainActivity.kt index e91461a..d1087d3 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/MainActivity.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/MainActivity.kt @@ -3,6 +3,8 @@ package io.flamenet.messenger.app import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.Image +import androidx.compose.ui.res.painterResource import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -25,6 +27,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll +import io.flamenet.messenger.R import io.flamenet.messenger.app.MessengerViewModel.Phase.Screen import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator @@ -349,15 +352,22 @@ private fun ColumnScope.SignIn(model: MessengerViewModel, phase: MessengerViewMo modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { - Box( + // The product's mark. This was an accent-coloured tile with a bold "F" + // in it — a placeholder from before there was a logo, and the first + // thing anybody sees. iOS carries the same image; see ios!144. + // + // ⚠️ Clipped to a rounded rectangle, and that is not decoration. The + // artwork's background is painted rather than transparent — the space + // behind the globe is part of the picture — so an unclipped mark is a + // hard-edged dark rectangle on a pale ground. Rounded, it matches the + // launcher icon the person just tapped. + Image( + painter = painterResource(R.drawable.brand_mark), + contentDescription = null, modifier = Modifier - .size(48.dp) - .clip(RoundedCornerShape(Radius.medium)) - .background(MaterialTheme.colorScheme.primary), - contentAlignment = Alignment.Center, - ) { - Text("F", fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color.White) - } + .size(72.dp) + .clip(RoundedCornerShape(Radius.medium)), + ) Spacer(Modifier.height(Space.lg)) Text("Hi there!", fontSize = 40.sp, fontWeight = FontWeight.Bold) Spacer(Modifier.height(Space.sm)) diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/Theme.kt b/app/src/main/kotlin/io/flamenet/messenger/app/Theme.kt index 42a6b31..868923f 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/Theme.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/Theme.kt @@ -73,10 +73,10 @@ object Palette { // it, and every piece of body text sits on this: ink reads 18.6:1 at the // top and 14.8:1 at the bottom, secondary text 8.7:1 and 6.9:1. A // saturated purple would look bolder in a screenshot and fight both. - val GroundTop = Color(0xFFF8F5FF) - val GroundBottom = Color(0xFFE4D8FB) - val NightGroundTop = Color(0xFF241645) - val NightGroundBottom = Color(0xFF0D0718) + val GroundTop = Color(0xFFE9DDFB) + val GroundBottom = Color(0xFFC6AFF2) + val NightGroundTop = Color(0xFF1C1036) + val NightGroundBottom = Color(0xFF070410) val Teal = Color(0xFF3BD5B2) // success and presence val Pink = Color(0xFFED497C) // the kit's alert accent @@ -86,7 +86,7 @@ object Palette { // ⚠️ Still needed where a gradient cannot go (a single Color parameter, a // system bar tint). This is the gradient's mid-tone, so the two never read // as different surfaces. - val Mist = Color(0xFFF1EAFE) // the page ground, flat + val Mist = Color(0xFFD8C6F6) // the page ground, flat val SurfaceAlt = Color(0xFFF3F3F3) // received bubbles, pills, bands val IncomingBubble = Color(0xFFF3F3F3) // ⚠️ A blue-tinted grey rather than the flat #F3F3F3: an initials avatar @@ -95,7 +95,22 @@ object Palette { // ⚠️ Lavender, not the old blue wash. A blue-tinted circle on a purple // ground reads as a bug rather than as a second accent. val AvatarFill = Color(0xFFEDE6FC) - val SecondaryText = Color(0xFF505C69) + // 🔴 **Darkened with the ground, not left behind it.** #505C69 read 5.0:1 + // on the old bottom stop and only 3.5:1 on the darker one — below AA, and + // most of the explanatory copy on these screens is set in it. Measured now: + // 8.4:1 at the top of the gradient, 5.6:1 at the bottom. + val SecondaryText = Color(0xFF413A4D) + // 🔴 **Dark mode had no secondary text colour at all**, and used the light + // one: #505C69 on the dark ground measures **2.41:1**, far below AA, and + // most of the explanatory copy on these screens is set in it. iOS has had + // a dark variant since it was written (`secondaryText` is a light/dark + // pair); Android used one value for both grounds and nothing noticed. + // + // Found by measuring while changing the palette, not by looking — a dim + // grey on a dark ground looks deliberate, which is exactly why it survived. + // #AFA4C9 is the same idea in the new hue: 7.04:1 on the lightest dark + // stop, 8.46:1 on the darkest. + val SecondaryTextDark = Color(0xFFAFA4C9) val Divider = Color(0x1A505C69) // Semantic @@ -105,7 +120,7 @@ object Palette { // Dark-mode grounds — these ARE the site's, since the site is dark. val InkSurface = Color(0xFF121A2E) // --panel - val NightGround = Color(0xFF150C26) // --bg, the dark gradient's mid-tone + val NightGround = Color(0xFF110A20) // --bg, the dark gradient's mid-tone } /** @@ -259,7 +274,7 @@ fun MessengerTheme(content: @Composable () -> Unit) { surfaceVariant = Palette.InkSurface, onBackground = Palette.Mist, onSurface = Palette.Mist, - onSurfaceVariant = Palette.SecondaryText, + onSurfaceVariant = Palette.SecondaryTextDark, error = Palette.Danger, outline = Palette.Divider, ) diff --git a/app/src/main/res/drawable-hdpi/brand_mark.png b/app/src/main/res/drawable-hdpi/brand_mark.png new file mode 100644 index 0000000..319e3bb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/brand_mark.png differ diff --git a/app/src/main/res/drawable-mdpi/brand_mark.png b/app/src/main/res/drawable-mdpi/brand_mark.png new file mode 100644 index 0000000..a8ef9be Binary files /dev/null and b/app/src/main/res/drawable-mdpi/brand_mark.png differ diff --git a/app/src/main/res/drawable-xhdpi/brand_mark.png b/app/src/main/res/drawable-xhdpi/brand_mark.png new file mode 100644 index 0000000..d11913e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/brand_mark.png differ diff --git a/app/src/main/res/drawable-xxhdpi/brand_mark.png b/app/src/main/res/drawable-xxhdpi/brand_mark.png new file mode 100644 index 0000000..b477c04 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/brand_mark.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/brand_mark.png b/app/src/main/res/drawable-xxxhdpi/brand_mark.png new file mode 100644 index 0000000..795f778 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/brand_mark.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index 6c028dc..3beb7ab 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 4ff696d..3ac986f 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index 844711a..bca12aa 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 64f42ff..cc27ff1 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index f1cf14d..653ae8c 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/test/kotlin/io/flamenet/messenger/app/PaletteMatchesIOSTest.kt b/app/src/test/kotlin/io/flamenet/messenger/app/PaletteMatchesIOSTest.kt index efe039b..5568b01 100644 --- a/app/src/test/kotlin/io/flamenet/messenger/app/PaletteMatchesIOSTest.kt +++ b/app/src/test/kotlin/io/flamenet/messenger/app/PaletteMatchesIOSTest.kt @@ -71,6 +71,24 @@ class PaletteMatchesIOSTest { "white on the gradient's light end is %.2f:1 — below AA for normal text".format(ratio)) } + /** + * 🔴 The dark grounds, which had NO secondary-text colour of their own until + * 2026-09-07: the light `#505C69` was used on both, and measured 2.41:1 on + * the dark ground. Nothing noticed, because a dim grey on a dark background + * looks deliberate. + */ + @Test + fun `body text clears AA on both ends of the DARK gradient`() { + for (ground in listOf(Palette.NightGroundTop, Palette.NightGroundBottom, + Palette.InkSurface)) { + val ink = contrast(ground, Palette.Mist) + assertTrue(ink >= 4.5, "dark-mode ink on ${androidHex(ground)} is %.2f:1".format(ink)) + val muted = contrast(ground, Palette.SecondaryTextDark) + assertTrue(muted >= 4.5, + "dark-mode secondary text on ${androidHex(ground)} is %.2f:1".format(muted)) + } + } + @Test fun `body text clears AA on both ends of the page gradient`() { for (ground in listOf(Palette.GroundTop, Palette.GroundBottom)) {