mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-15 06:56:30 +00:00
ui: mute theme-aware peer colors for contrast
Extract PeerColorStyle so each palette owns saturation/value, keeping hues stable while dark mode stays bright-but-muted and light mode avoids neon labels. New themes only need to supply their own style. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
c39bb8124c
commit
719b3d1895
@ -41,10 +41,11 @@ data class BitchatPalette(
|
||||
val accentPurple: Color,
|
||||
|
||||
// MARK: - Deterministic peer colors
|
||||
/** Chroma applied after deriving a peer's stable hue. */
|
||||
val peerColorSaturation: Float,
|
||||
/** Brightness applied after deriving a peer's stable hue. */
|
||||
val peerColorValue: Float,
|
||||
/**
|
||||
* Saturation/value applied after deriving a peer's stable hue. Swap this when adding a
|
||||
* new theme — see [PeerColorStyle] for contrast guidelines.
|
||||
*/
|
||||
val peerColors: PeerColorStyle,
|
||||
)
|
||||
|
||||
val DarkBitchatPalette = BitchatPalette(
|
||||
@ -56,8 +57,7 @@ val DarkBitchatPalette = BitchatPalette(
|
||||
textTertiary = Color(0xFF6B776B),
|
||||
accentOrange = Color(0xFFFF9F0A),
|
||||
accentPurple = Color(0xFFBF5AF2),
|
||||
peerColorSaturation = 1f,
|
||||
peerColorValue = 1f,
|
||||
peerColors = PeerColorStyle.Dark,
|
||||
)
|
||||
|
||||
val LightBitchatPalette = BitchatPalette(
|
||||
@ -69,8 +69,7 @@ val LightBitchatPalette = BitchatPalette(
|
||||
textTertiary = Color(0xFF757F75),
|
||||
accentOrange = Color(0xFFFF9500),
|
||||
accentPurple = Color(0xFFAF52DE),
|
||||
peerColorSaturation = 0.85f,
|
||||
peerColorValue = 0.45f,
|
||||
peerColors = PeerColorStyle.Light,
|
||||
)
|
||||
|
||||
val LocalBitchatPalette = staticCompositionLocalOf { DarkBitchatPalette }
|
||||
|
||||
@ -1,9 +1,36 @@
|
||||
package com.bitchat.android.ui.theme
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.bitchat.android.ui.PeerIdentity
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Theme-specific chroma applied after a peer's stable hue is derived.
|
||||
*
|
||||
* Hue stays identity-stable across themes (and byte-identical to iOS). Only saturation
|
||||
* and value change so peer labels remain readable on each background.
|
||||
*
|
||||
* Guidelines when adding a future theme:
|
||||
* - Dim / dark backgrounds: keep [value] high so colors are not lost against the surface;
|
||||
* prefer muted [saturation] over neon.
|
||||
* - Light backgrounds: keep [value] moderate-low so colors are not blinding; avoid
|
||||
* near-full saturation.
|
||||
*/
|
||||
@Immutable
|
||||
data class PeerColorStyle(
|
||||
val saturation: Float,
|
||||
val value: Float,
|
||||
) {
|
||||
companion object {
|
||||
/** Soft pastels that stay bright enough on near-black chat surfaces. */
|
||||
val Dark = PeerColorStyle(saturation = 0.55f, value = 0.82f)
|
||||
|
||||
/** Deeper, less saturated tones that stay readable on near-white surfaces. */
|
||||
val Light = PeerColorStyle(saturation = 0.70f, value = 0.42f)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The single identity-to-color boundary used by chat, people sheets, and mentions.
|
||||
*
|
||||
@ -22,9 +49,10 @@ fun colorForPeer(identity: PeerIdentity, palette: BitchatPalette): Color {
|
||||
hue = (hue + 0.12) % 1.0
|
||||
}
|
||||
|
||||
val style = palette.peerColors
|
||||
return Color.hsv(
|
||||
hue = (hue * 360).toFloat(),
|
||||
saturation = palette.peerColorSaturation,
|
||||
value = palette.peerColorValue
|
||||
saturation = style.saturation,
|
||||
value = style.value
|
||||
)
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import com.bitchat.android.ui.theme.LightBitchatColorScheme
|
||||
import com.bitchat.android.ui.theme.LightBitchatPalette
|
||||
import com.bitchat.android.ui.theme.MessageBodyTextStyle
|
||||
import com.bitchat.android.ui.theme.MessageSenderTextStyle
|
||||
import com.bitchat.android.ui.theme.PeerColorStyle
|
||||
import com.bitchat.android.ui.theme.colorForPeer
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@ -444,8 +445,8 @@ class ChatUIUtilsTest {
|
||||
|
||||
@Test
|
||||
fun `peer color hue is stable across light and dark, only chroma differs`() {
|
||||
// Hue derivation must stay byte-identical to iOS; only saturation/value are tuned for
|
||||
// the redesigned neutral message body.
|
||||
// Hue derivation must stay byte-identical to iOS; only saturation/value are tuned per
|
||||
// theme so dark mode stays muted-but-bright and light mode stays deep-but-readable.
|
||||
val identity = PeerIdentity.mesh("abc")
|
||||
val dark = colorForPeer(identity, DarkBitchatPalette)
|
||||
val light = colorForPeer(identity, LightBitchatPalette)
|
||||
@ -456,10 +457,16 @@ class ChatUIUtilsTest {
|
||||
rgbToHsv(light.red, light.green, light.blue, lightHsv)
|
||||
|
||||
assertEquals(darkHsv[0].toDouble(), lightHsv[0].toDouble(), 1.0)
|
||||
assertEquals(1.0, darkHsv[1].toDouble(), 0.01)
|
||||
assertEquals(1.0, darkHsv[2].toDouble(), 0.01)
|
||||
assertEquals(0.85, lightHsv[1].toDouble(), 0.01)
|
||||
assertEquals(0.45, lightHsv[2].toDouble(), 0.01)
|
||||
assertEquals(PeerColorStyle.Dark.saturation.toDouble(), darkHsv[1].toDouble(), 0.01)
|
||||
assertEquals(PeerColorStyle.Dark.value.toDouble(), darkHsv[2].toDouble(), 0.01)
|
||||
assertEquals(PeerColorStyle.Light.saturation.toDouble(), lightHsv[1].toDouble(), 0.01)
|
||||
assertEquals(PeerColorStyle.Light.value.toDouble(), lightHsv[2].toDouble(), 0.01)
|
||||
// Dark theme: muted chroma, never dark (readable on near-black).
|
||||
assertTrue(darkHsv[1] < 0.75f)
|
||||
assertTrue(darkHsv[2] >= 0.75f)
|
||||
// Light theme: avoid neon / near-white peer labels.
|
||||
assertTrue(lightHsv[1] < 0.85f)
|
||||
assertTrue(lightHsv[2] <= 0.55f)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -485,7 +492,7 @@ class ChatUIUtilsTest {
|
||||
assertEquals(Color(0xFFF5F5F5), DarkBitchatColorScheme.onSurface)
|
||||
assertTrue(LightBitchatColorScheme.onSurface != DarkBitchatColorScheme.onSurface)
|
||||
assertTrue(
|
||||
LightBitchatPalette.peerColorValue != DarkBitchatPalette.peerColorValue
|
||||
LightBitchatPalette.peerColors != DarkBitchatPalette.peerColors
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user