Skip to content

Add "Petal" and "Mint" chat colors, and generalize outgoing text/icon contrast to work with any light chat color - #14910

Open
br4yd wants to merge 11 commits into
signalapp:mainfrom
br4yd:add-petal-chat-color
Open

Add "Petal" and "Mint" chat colors, and generalize outgoing text/icon contrast to work with any light chat color#14910
br4yd wants to merge 11 commits into
signalapp:mainfrom
br4yd:add-petal-chat-color

Conversation

@br4yd

@br4yd br4yd commented Aug 3, 2026

Copy link
Copy Markdown

First time contributor checklist

  • I have read how to contribute to this project
  • I have signed the Contributor License Agreement

Contributor checklist

  • I am following the Code Style Guidelines
  • I have tested my contribution on these devices:
    • Samsung Galaxy S26 Ultra, Android 17
    • Google Pixel 10 Pro, Android 17
  • My contribution is fully baked and ready to be merged as is
  • I ensure that all the open issues my contribution fixes are mentioned in the commit message of my first commit using the Fixes #1234 syntax

Description

A friend of mine asked for this specific pink as a chat color - she uses it on other messengers. Signal's custom color picker can't produce it because it deliberately keeps colors dark enough to guarantee contrast with the white outgoing-bubble text.

This adds Petal (#FCCDD9) as the first light preset in ChatColorsPalette. Since the existing text/icon colors assume a dark bubble, I added ChatColors.needsDarkText(), which switches outgoing body text, footer text/icons, and confirm/send button tints to black wherever the bubble color needs it.

Edit (2026-08-04): needsDarkText() originally only special-cased Petal's exact color. Based on feedback while testing, I generalized it to a relative-luminance check instead. See the commit "Generalize dark-text detection to luminance instead of a specific color" and the comment below. Any future light preset now gets correct contrast automatically, without touching the ~10 places that already call needsDarkText(). I also added "Mint" (#C8F0C0) as a second light preset in a single, self-contained commit to demonstrate exactly what that takes going forward.

I also added SOLID_PETAL = 23 to ChatStyle.BubbleColorPreset in lib/archive/src/main/protowire/Backup.proto so the color round-trips through backup/restore, with a fallback to ULTRAMARINE for unrecognized values on import.

Note for maintainers: Backup.proto is a cross-platform backup format. Each client (Android, iOS, Desktop) maintains its own copy of it rather than a shared source of truth. I checked both the Signal-iOS and Signal-Desktop repos directly:

  • Value 23 is currently unclaimed in both - their BubbleColorPreset enums stop at GRADIENT_TANGERINE = 22.
  • Both restore paths handle unrecognized preset values gracefully today: iOS's BackupArchiveChatStyleArchiver.asPaletteChatColor() maps unknown/unrecognized presets to nil (the thread falls back to the default "Auto" color), and Desktop's backup-import restore logic (ts/services/backups/import.preload.ts) falls into a default: branch that resolves to 'ultramarine'. So restoring a Petal-colored backup on current iOS or Desktop won't crash or corrupt anything - it just silently downgrades to a default color, without notifying the user.

br4yd added 8 commits August 3, 2026 15:24
… button

The needsDarkText() fallback introduced for the Petal chat color missed two
spots: the document/file attachment view's filename and size text (colors
were baked into layout XML, not recomputed per recipient), and the confirm
(checkmark) button shown when adding a caption to media before sending.
Outgoing polls used the same fixed white/near-white color resources
regardless of chat color, so options, vote counts, checkboxes, and
progress fill stayed unreadable on the light Petal chat color.
Outgoing audio bubbles always used the same fixed foreground tint
(duration text, seek bar, play icon), so voice messages stayed
unreadable on the light Petal chat color.
…atus

The waveform bar/thumb colors on outgoing audio bubbles were baked into
static XML attributes and never updated for the dark-text case, so only
the play button and seek thumb (previously fixed) turned black while the
waveform itself stayed white.

Stickers use a separate footer view (no bubble background) that is
colored in setFooter(), which runs after setBubbleState() and was
unconditionally overwriting the dark-text color back to white whenever a
wallpaper was set.
The button label reused the chat color itself as its text color, which
is illegible on the light Petal background since the label sits on a
near-white pill.
The per-photo selection number badge and the gallery's send/count button
only tinted their background with the chat color, leaving their text a
fixed light color that disappears against the light Petal background.
The confirm checkmark's background was tinted with the chat color, but
its icon tint was hardcoded white in the layout.
@br4yd

br4yd commented Aug 4, 2026

Copy link
Copy Markdown
Author

While using the app with this color I found some other places where it used the wrong contrast and still used white as the text color or icon color for buttons that send/confirm things. The last commits are fixing this.

needsDarkText() only matched Petal's exact color, so any future light
chat color would silently render with the wrong (light) text/icons
everywhere that already checks this flag.
@br4yd

br4yd commented Aug 4, 2026

Copy link
Copy Markdown
Author

While testing Petal I found several more spots that still hardcoded white instead of relying on needsDarkText(), or hardcoded contrast against the specific singleColor value (see the last few commits): voice message waveform colors, the poll "view votes" button, the sticker send-status footer, media picker selection badges + send button, and the location-picker confirm button.

That made something clear: needsDarkText() was checking singleColor == ChatColorsPalette.Bubbles.PETAL.singleColor so basically an equality check against one specific color. It only worked because I'd manually tracked down every place text/icons get drawn on a chat color and added a branch for exactly this one color. Anyone adding a second light preset later would've had to rediscover the same ~10 call sites from scratch, with nothing pointing them there.

I replaced that equality check with a relative-luminance threshold (ChatColors.kt, DARK_TEXT_LUMINANCE_THRESHOLD = 0.4): existing solids all sit under ~0.2 luminance, Petal is ~0.69, so 0.4 cleanly separates "needs dark text" from "doesn't," with margin on both sides. Since every one of those ~10 call sites already just calls needsDarkText() instead of comparing colors itself, this fix is fully retroactive. None of them needed to change.

Concretely, adding another light preset later i.e. a pastel mint green would only need:

// ChatColorsPalette.kt
val MINT = ChatColors.forColor(ChatColors.Id.BuiltIn, 0xFFC8F0C0.toInt())
// ...added to the `solids` list

plus the usual proto enum value and ChatStyleConverter mapping for backup round-tripping. 0xC8F0C0 has a luminance of ~0.78, comfortably above the 0.4 threshold, so needsDarkText() returns true for it automatically. No changes needed anywhere else. This makes it much easier and future-proof for future implementations of chat designs.

@br4yd br4yd changed the title Add "Petal" chat color and fix outgoing text/icon contrast for light colors Add "Petal" chat color and generalize outgoing text/icon contrast to work with any light chat color Aug 4, 2026
Demonstrates that adding a light preset now only takes a palette entry
plus the usual proto/backup mapping, with no changes needed anywhere
text/icons are drawn on a chat color, since needsDarkText() is
luminance-based rather than Petal-specific.
@br4yd br4yd changed the title Add "Petal" chat color and generalize outgoing text/icon contrast to work with any light chat color Add "Petal" and "Mint" chat colors, and generalize outgoing text/icon contrast to work with any light chat color Aug 4, 2026
@br4yd

br4yd commented Aug 4, 2026

Copy link
Copy Markdown
Author

I also added a second light preset, "Mint" (#C8F0C0). Partly because it turned out to look genuinely good in the UI, and partly as a proof of concept for the luminance-based contrast fix from the previous commit.

The whole addition is a single commit (1820072): one palette entry, one proto enum value (SOLID_MINT = 24), and one ChatStyleConverter mapping for backup round-tripping. Nothing in any of the ~10 places that render text/icons on top of a chat color needed to change.

That's the concrete payoff of moving needsDarkText() off the Petal-specific equality check. So adding another light color is now exactly as easy as adding any other solid preset, which was the whole point of generalizing it.

If this gets merged I also plan to improve the color picker in the chat design settings to allow using custom chat colors that are lighter making use of the luminance-based contrast check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant