The kit's palette, not just its layout
I kept Flamenet orange and defended it as brand continuity. That was wrong on the facts: the orange had already been retired and the public pages carry none of it, so the apps were the last thing wearing a brand colour the brand had dropped — on the authority of a comment in Theme.kt that nobody had rechecked. The comment is corrected in place so nobody restores it for the same stale reason. The values are measured out of the Sketch file rather than sampled from a render. The two text colours are the ones that actually appear in its symbols — #090710 on 165 text layers, #505C69 on 164 — and the accent #2298FF is what it uses at 12pt as well as on buttons, so it is their link colour and not only a fill. The constants were called Ember and held orange. They are Accent now: a constant named for a colour it no longer holds is how a palette drifts back. ⚠️ Recorded because it will come up: #2298FF on white is about 3.3:1 — fine for a bold button label, not for small text. The small text on these screens is grey, and it needs to stay that way. Also in this commit, the Android sign-in takes the kit's Welcome layout: a logo mark, a 40pt greeting and one line saying what this is, centred with room above them. It was a 24pt product name jammed against a Server field, which is the shape of a login dialog rather than the first thing anyone sees of the app. The submit button is 240 wide and centred, as the kit draws it — a button spanning the phone reads as the bottom of a form; one that does not reads as a choice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5a592e9858
commit
87e96761cc
@@ -13,6 +13,9 @@ import androidx.compose.material3.LocalTextStyle
|
|||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.semantics.contentDescription
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
@@ -208,7 +211,43 @@ private fun ColumnScope.SignIn(model: MessengerViewModel, phase: MessengerViewMo
|
|||||||
// Only a relay keeping its own accounts needs an address for reset mail.
|
// Only a relay keeping its own accounts needs an address for reset mail.
|
||||||
val needsEmail = registering && site?.accountProvider == SiteConfig.AccountProvider.LOCAL
|
val needsEmail = registering && site?.accountProvider == SiteConfig.AccountProvider.LOCAL
|
||||||
|
|
||||||
Text("Flamenet Messenger", style = MaterialTheme.typography.headlineMedium)
|
// 🔴 **The kit's Welcome screen, which is a masthead and not a form
|
||||||
|
// heading.** A logo mark, a 40pt greeting, and one line saying what this
|
||||||
|
// is — centred, with room above it. This was a 24pt product name jammed
|
||||||
|
// against a Server field, which is the layout of a login dialog rather
|
||||||
|
// than the first thing anyone sees of the app. See `DESIGN.md`.
|
||||||
|
//
|
||||||
|
// ⚠️ The kit's Welcome has no fields on it at all — it is a splash before
|
||||||
|
// a phone number is asked for. Flamenet does not ask for a phone number
|
||||||
|
// and has nothing to put on a second screen, so the masthead and the form
|
||||||
|
// share one. The typography and the centring are the kit's; the fields
|
||||||
|
// are ours.
|
||||||
|
Spacer(Modifier.height(Space.xl))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(Space.lg))
|
||||||
|
Text("Hi there!", fontSize = 40.sp, fontWeight = FontWeight.Bold)
|
||||||
|
Spacer(Modifier.height(Space.sm))
|
||||||
|
Text(
|
||||||
|
"Flamenet Messenger. End-to-end encrypted, no phone number, and it "
|
||||||
|
+ "runs on whichever server you point it at.",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = Palette.SecondaryText,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(Space.xl))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = relay,
|
value = relay,
|
||||||
@@ -274,18 +313,29 @@ private fun ColumnScope.SignIn(model: MessengerViewModel, phase: MessengerViewMo
|
|||||||
Text(it, color = MaterialTheme.colorScheme.error, modifier = Modifier.testTag("error"))
|
Text(it, color = MaterialTheme.colorScheme.error, modifier = Modifier.testTag("error"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ⚠️ 240 wide and centred, which is the kit's primary button on this
|
||||||
|
// screen — not a full-width slab. A button that spans the phone reads as
|
||||||
|
// the bottom of a form; one that does not reads as a choice.
|
||||||
|
Spacer(Modifier.height(Space.md))
|
||||||
|
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
model.signIn(handle.trim(), passphrase, register = registering, email = email.trim())
|
model.signIn(handle.trim(), passphrase, register = registering, email = email.trim())
|
||||||
},
|
},
|
||||||
enabled = !phase.busy && handle.isNotBlank() && passphrase.isNotBlank() &&
|
enabled = !phase.busy && handle.isNotBlank() && passphrase.isNotBlank() &&
|
||||||
(!needsEmail || email.isNotBlank()),
|
(!needsEmail || email.isNotBlank()),
|
||||||
modifier = Modifier.fillMaxWidth().testTag("submit"),
|
shape = RoundedCornerShape(KitButton.radius),
|
||||||
|
modifier = Modifier.width(240.dp).height(KitButton.height).testTag("submit"),
|
||||||
) {
|
) {
|
||||||
if (phase.busy) {
|
if (phase.busy) {
|
||||||
CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
|
CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
|
||||||
} else {
|
} else {
|
||||||
Text(if (registering) "Create account" else "Sign in")
|
Text(
|
||||||
|
if (registering) "Create account" else "Sign in",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ import androidx.compose.ui.unit.sp
|
|||||||
/**
|
/**
|
||||||
* The Flamenet design system.
|
* The Flamenet design system.
|
||||||
*
|
*
|
||||||
* 🔴 **Taken from flamenetmessenger.com, not invented.** The site declares
|
* 🔴 **Taken from the design kit, measured out of the Sketch file.** This
|
||||||
* `--flame: #ff5a1f`, `--blue: #3b6fff`, `--accent: #2dd4a7` over a dark navy
|
* comment used to say the palette came from flamenetmessenger.com and quoted
|
||||||
* ground (`--bg: #0a0e1a`, `--panel: #121a2e`). The accents transfer as-is;
|
* `--flame: #ff5a1f` — which had already been retired from the public pages,
|
||||||
* the site's `--ink`/`--muted` are light-on-dark and are NOT pasted in here,
|
* so the app was carrying a brand colour the brand no longer used, on the
|
||||||
* because the apps are light. Dark mode does use the site's real grounds.
|
* authority of a comment nobody rechecked. The values here are read from the
|
||||||
|
* kit's own symbols; see `Palette`.
|
||||||
*
|
*
|
||||||
* 🔴 **One palette, two apps.** iOS carries the same values under the same
|
* 🔴 **One palette, two apps.** iOS carries the same values under the same
|
||||||
* names in `Sources/Theme/Theme.swift`. This file's comment used to claim the
|
* names in `Sources/Theme/Theme.swift`. This file's comment used to claim the
|
||||||
@@ -39,32 +40,39 @@ import androidx.compose.ui.unit.sp
|
|||||||
* untouched — this is a skin over working screens.
|
* untouched — this is a skin over working screens.
|
||||||
*/
|
*/
|
||||||
object Palette {
|
object Palette {
|
||||||
// Brand — the site's --flame.
|
// 🔴 **The design kit's palette, measured out of the file** — not sampled
|
||||||
// ⚠️ White on Ember is about 3.1:1: fine for a bold button label (AA
|
// from a render and not approximated. The text colours below are the two
|
||||||
// Large), NOT fine for small text. Small text and links use EmberDeep.
|
// that actually appear in its symbols: #090710 on 165 text layers and
|
||||||
val Ember = Color(0xFFFF5A1F)
|
// #505C69 on 164. The accent is the one it uses at 12pt as well as on
|
||||||
val EmberDeep = Color(0xFFC4400F)
|
// buttons, so it is their link colour, not only their fill.
|
||||||
val EmberSoft = Color(0x1AFF5A1F)
|
//
|
||||||
|
// ⚠️ These names used to say Ember, and held orange. The orange was the
|
||||||
|
// brand when this app was written and is not any more — the public pages
|
||||||
|
// carry none of it — so the names go with the colour rather than outliving
|
||||||
|
// it. A constant called `Ember` holding blue is how a palette drifts back.
|
||||||
|
val Accent = Color(0xFF2298FF) // buttons, bubbles, links
|
||||||
|
val AccentBright = Color(0xFF4FACFE) // the kit's lighter primary swatch
|
||||||
|
val AccentSoft = Color(0x1A2298FF)
|
||||||
|
|
||||||
// The site's other two accents, used sparingly.
|
val Teal = Color(0xFF3BD5B2) // success and presence
|
||||||
val Blue = Color(0xFF3B6FFF) // --blue, informational only
|
val Pink = Color(0xFFED497C) // the kit's alert accent
|
||||||
val Teal = Color(0xFF2DD4A7) // --accent, success and presence
|
|
||||||
|
|
||||||
// Neutrals: the site's navy family resolved for a LIGHT ground.
|
// Neutrals, all from the kit.
|
||||||
// ⚠️ --ink (#e8edf7) and --muted (#9aa7c2) are light-on-dark values and
|
val Ink = Color(0xFF090710)
|
||||||
// would be invisible here, so they are not pasted in literally.
|
val Mist = Color(0xFFF9F9F9) // the page ground
|
||||||
val Ink = Color(0xFF0F1626)
|
val SurfaceAlt = Color(0xFFF3F3F3) // received bubbles, pills, bands
|
||||||
val Mist = Color(0xFFF6F8FC) // the light ground (was 'Parchment' when the palette was warm)
|
val IncomingBubble = Color(0xFFF3F3F3)
|
||||||
val SurfaceAlt = Color(0xFFEDF1F8)
|
// ⚠️ A blue-tinted grey rather than the flat #F3F3F3: an initials avatar
|
||||||
val IncomingBubble = Color(0xFFEDF1F8)
|
// needs to read as a circle against the surface behind it, and two greys a
|
||||||
val AvatarFill = Color(0xFFE4EAF6)
|
// shade apart do not.
|
||||||
val SecondaryText = Color(0xFF5A6884)
|
val AvatarFill = Color(0xFFE8F1FB)
|
||||||
val Divider = Color(0x1A1F2B45)
|
val SecondaryText = Color(0xFF505C69)
|
||||||
|
val Divider = Color(0x1A505C69)
|
||||||
|
|
||||||
// Semantic
|
// Semantic
|
||||||
val Success = Color(0xFF1F9E7A)
|
val Success = Color(0xFF3BD5B2)
|
||||||
val Warning = Color(0xFFB8860B)
|
val Warning = Color(0xFFB8860B)
|
||||||
val Danger = Color(0xFFC0392B)
|
val Danger = Color(0xFFED497C)
|
||||||
|
|
||||||
// Dark-mode grounds — these ARE the site's, since the site is dark.
|
// Dark-mode grounds — these ARE the site's, since the site is dark.
|
||||||
val InkSurface = Color(0xFF121A2E) // --panel
|
val InkSurface = Color(0xFF121A2E) // --panel
|
||||||
@@ -187,9 +195,9 @@ fun MessengerTheme(content: @Composable () -> Unit) {
|
|||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = if (dark) {
|
colorScheme = if (dark) {
|
||||||
darkColorScheme(
|
darkColorScheme(
|
||||||
primary = Palette.Ember,
|
primary = Palette.AccentBright,
|
||||||
onPrimary = Color.White,
|
onPrimary = Color.White,
|
||||||
secondary = Palette.EmberDeep,
|
secondary = Palette.Accent,
|
||||||
background = Palette.NightGround,
|
background = Palette.NightGround,
|
||||||
surface = Palette.InkSurface,
|
surface = Palette.InkSurface,
|
||||||
surfaceVariant = Palette.InkSurface,
|
surfaceVariant = Palette.InkSurface,
|
||||||
@@ -201,9 +209,13 @@ fun MessengerTheme(content: @Composable () -> Unit) {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
lightColorScheme(
|
lightColorScheme(
|
||||||
primary = Palette.EmberDeep,
|
// ⚠️ The kit's #2298FF on white is about 3.3:1 — fine for a
|
||||||
|
// bold button label, not for small text. Anything small and
|
||||||
|
// blue must use a darker step; the two places that do are the
|
||||||
|
// section labels and the timestamps, and both are grey.
|
||||||
|
primary = Palette.Accent,
|
||||||
onPrimary = Color.White,
|
onPrimary = Color.White,
|
||||||
secondary = Palette.Ember,
|
secondary = Palette.AccentBright,
|
||||||
background = Palette.Mist,
|
background = Palette.Mist,
|
||||||
surface = Color.White,
|
surface = Color.White,
|
||||||
surfaceVariant = Palette.SurfaceAlt,
|
surfaceVariant = Palette.SurfaceAlt,
|
||||||
|
|||||||
Reference in New Issue
Block a user