From 507ae528e3e9497d03e7073c80b454d7d446ce03 Mon Sep 17 00:00:00 2001 From: Gerard Paligot Date: Fri, 19 Dec 2025 16:47:51 +0100 Subject: [PATCH] Rework DaxText Composable contract --- .../ui/palette/ColorPaletteFragment.kt | 6 +- .../common/ui/compose/text/DaxText.kt | 31 ++-- .../common/ui/compose/theme/Theme.kt | 19 +- .../common/ui/compose/theme/Typography.kt | 162 +++++++----------- .../lint/ui/DaxTextColorUsageDetector.kt | 18 +- .../lint/ui/DaxTextColorUsageDetectorTest.kt | 48 +++--- 6 files changed, 129 insertions(+), 155 deletions(-) diff --git a/android-design-system/design-system-internal/src/main/java/com/duckduckgo/common/ui/internal/ui/palette/ColorPaletteFragment.kt b/android-design-system/design-system-internal/src/main/java/com/duckduckgo/common/ui/internal/ui/palette/ColorPaletteFragment.kt index 9915d892d518..05fcc7f58f8c 100644 --- a/android-design-system/design-system-internal/src/main/java/com/duckduckgo/common/ui/internal/ui/palette/ColorPaletteFragment.kt +++ b/android-design-system/design-system-internal/src/main/java/com/duckduckgo/common/ui/internal/ui/palette/ColorPaletteFragment.kt @@ -151,7 +151,7 @@ class ColorPaletteFragment : Fragment() { DaxColorAttributeListItem( text = "Primary Text", dotColors = DaxColorDotColors( - fillColor = DuckDuckGoTheme.textColors.primary, + fillColor = DuckDuckGoTheme.colors.text.primary, strokeColor = DuckDuckGoTheme.colors.backgrounds.backgroundInverted, ), ) @@ -162,7 +162,7 @@ class ColorPaletteFragment : Fragment() { DaxColorAttributeListItem( text = "Secondary Text", dotColors = DaxColorDotColors( - fillColor = DuckDuckGoTheme.textColors.secondary, + fillColor = DuckDuckGoTheme.colors.text.secondary, strokeColor = DuckDuckGoTheme.colors.backgrounds.backgroundInverted, ), ) @@ -173,7 +173,7 @@ class ColorPaletteFragment : Fragment() { DaxColorAttributeListItem( text = "Text Disabled", dotColors = DaxColorDotColors( - fillColor = DuckDuckGoTheme.textColors.disabled, + fillColor = DuckDuckGoTheme.colors.text.disabled, strokeColor = DuckDuckGoTheme.colors.backgrounds.backgroundInverted, ), ) diff --git a/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/text/DaxText.kt b/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/text/DaxText.kt index df3cc2a2c6b4..4c5b1f7d2533 100644 --- a/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/text/DaxText.kt +++ b/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/text/DaxText.kt @@ -16,24 +16,26 @@ package com.duckduckgo.common.ui.compose.text +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.takeOrElse +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.duckduckgo.common.ui.compose.theme.DuckDuckGoTextStyle import com.duckduckgo.common.ui.compose.theme.DuckDuckGoTheme -import com.duckduckgo.common.ui.compose.theme.asTextStyle import com.duckduckgo.common.ui.compose.tools.PreviewBox import com.duckduckgo.common.ui.compose.tools.PreviewBoxInverted /** * Base text component for the DuckDuckGo design system. * - * @param color The text color. Should use colors from [DuckDuckGoTheme.textColors] for consistency - * with the design system (e.g., [DuckDuckGoTheme.textColors.primary], [DuckDuckGoTheme.textColors.secondary]). + * @param color The text color. Should use colors from [DuckDuckGoTheme.colors.text] for consistency + * with the design system (e.g., [DuckDuckGoTheme.colors.text.primary], [DuckDuckGoTheme.colors.text.secondary]). * A lint rule will warn if arbitrary colors are used. * * Asana Task: https://app.asana.com/1/137249556945/project/1202857801505092/task/1211634956773768 @@ -43,16 +45,17 @@ import com.duckduckgo.common.ui.compose.tools.PreviewBoxInverted fun DaxText( text: String, modifier: Modifier = Modifier, - style: DuckDuckGoTextStyle = DuckDuckGoTheme.typography.body1, - color: Color = DuckDuckGoTheme.textColors.primary, + color: Color = Color.Unspecified, + style: TextStyle = LocalTextStyle.current, textAlign: TextAlign? = null, overflow: TextOverflow = TextOverflow.Ellipsis, maxLines: Int = Int.MAX_VALUE, ) { + val textColor = color.takeOrElse { style.color.takeOrElse { LocalContentColor.current } } Text( text = text, - color = color, - style = style.asTextStyle, + color = textColor, + style = style, textAlign = textAlign, overflow = overflow, maxLines = maxLines, @@ -170,7 +173,7 @@ private fun DaxTextColorPrimaryPreview() { PreviewBox { DaxText( text = "Primary Color", - color = DuckDuckGoTheme.textColors.primary, + color = DuckDuckGoTheme.colors.text.primary, ) } } @@ -181,7 +184,7 @@ private fun DaxTextColorPrimaryInvertedPreview() { PreviewBoxInverted { DaxText( text = "Primary Inverted", - color = DuckDuckGoTheme.textColors.primaryInverted, + color = DuckDuckGoTheme.colors.text.primaryInverted, ) } } @@ -192,7 +195,7 @@ private fun DaxTextColorSecondaryPreview() { PreviewBox { DaxText( text = "Secondary Color", - color = DuckDuckGoTheme.textColors.secondary, + color = DuckDuckGoTheme.colors.text.secondary, ) } } @@ -203,7 +206,7 @@ private fun DaxTextColorSecondaryInvertedPreview() { PreviewBoxInverted { DaxText( text = "Secondary Inverted", - color = DuckDuckGoTheme.textColors.secondaryInverted, + color = DuckDuckGoTheme.colors.text.secondaryInverted, ) } } @@ -214,7 +217,7 @@ private fun DaxTextColorTertiaryPreview() { PreviewBox { DaxText( text = "Tertiary Color", - color = DuckDuckGoTheme.textColors.tertiary, + color = DuckDuckGoTheme.colors.text.tertiary, ) } } @@ -225,7 +228,7 @@ private fun DaxTextColorDisabledPreview() { PreviewBox { DaxText( text = "Disabled Color", - color = DuckDuckGoTheme.textColors.disabled, + color = DuckDuckGoTheme.colors.text.disabled, ) } } diff --git a/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Theme.kt b/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Theme.kt index fabb891ae7e1..821740d2fa79 100644 --- a/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Theme.kt +++ b/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Theme.kt @@ -19,7 +19,9 @@ package com.duckduckgo.common.ui.compose.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.material3.ColorScheme +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Shapes import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -36,11 +38,6 @@ object DuckDuckGoTheme { @ReadOnlyComposable get() = LocalDuckDuckGoColors.current - val textColors: DuckDuckGoTextColors - @Composable - @ReadOnlyComposable - get() = colors.text - val shapes @Composable @ReadOnlyComposable @@ -177,7 +174,17 @@ fun DuckDuckGoTheme( colorScheme = debugColors(), typography = debugTypography(), shapes = debugShapes, - content = content, + content = { + ProvideTextStyle( + LocalDuckDuckGoTypography.current.body1, + content = { + CompositionLocalProvider( + LocalContentColor provides colors.text.primary, + content = content, + ) + }, + ) + }, ) } } diff --git a/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Typography.kt b/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Typography.kt index 9d84cefc7ed7..883677b79361 100644 --- a/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Typography.kt +++ b/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/compose/theme/Typography.kt @@ -39,110 +39,78 @@ private val RobotoMono = FontFamily( @Immutable data class DuckDuckGoTypography( - val title: DuckDuckGoTextStyle = - DuckDuckGoTextStyle( - TextStyle( - fontSize = 32.sp, - lineHeight = 36.sp, - fontWeight = FontWeight.Bold, - ), - ), - - val h1: DuckDuckGoTextStyle = - DuckDuckGoTextStyle( - TextStyle( - fontSize = 24.sp, - lineHeight = 30.sp, - fontWeight = FontWeight.Bold, - ), - ), - - val h2: DuckDuckGoTextStyle = - DuckDuckGoTextStyle( - TextStyle( - fontSize = 20.sp, - lineHeight = 24.sp, - letterSpacing = 0.3.sp, - fontWeight = FontWeight.Medium, - ), - ), - - val h3: DuckDuckGoTextStyle = - DuckDuckGoTextStyle( - TextStyle( - fontSize = 16.sp, - lineHeight = 21.sp, - fontWeight = FontWeight.Medium, - ), - ), - - val h4: DuckDuckGoTextStyle = - DuckDuckGoTextStyle( - TextStyle( - fontSize = 14.sp, - lineHeight = 20.sp, - letterSpacing = 0.3.sp, - fontWeight = FontWeight.Medium, - ), - ), - - val h5: DuckDuckGoTextStyle = - DuckDuckGoTextStyle( - TextStyle( - fontSize = 13.sp, - lineHeight = 16.sp, - fontWeight = FontWeight.Medium, - ), - ), - - val body1: DuckDuckGoTextStyle = DuckDuckGoTextStyle( - TextStyle( - fontSize = 16.sp, - lineHeight = 20.sp, - ), + val title: TextStyle = TextStyle( + fontSize = 32.sp, + lineHeight = 36.sp, + fontWeight = FontWeight.Bold, ), - val body1Bold: DuckDuckGoTextStyle = DuckDuckGoTextStyle( - body1.textStyle.copy( - fontWeight = FontWeight.Bold, - ), + val h1: TextStyle = TextStyle( + fontSize = 24.sp, + lineHeight = 30.sp, + fontWeight = FontWeight.Bold, ), - val body1Mono: DuckDuckGoTextStyle = DuckDuckGoTextStyle( - body1.textStyle.copy( - fontFamily = RobotoMono, - ), + val h2: TextStyle = TextStyle( + fontSize = 20.sp, + lineHeight = 24.sp, + letterSpacing = 0.3.sp, + fontWeight = FontWeight.Medium, ), - val body2: DuckDuckGoTextStyle = DuckDuckGoTextStyle( - TextStyle( - fontSize = 14.sp, - lineHeight = 18.sp, - letterSpacing = 0.2.sp, - ), + val h3: TextStyle = TextStyle( + fontSize = 16.sp, + lineHeight = 21.sp, + fontWeight = FontWeight.Medium, ), - val body2Bold: DuckDuckGoTextStyle = DuckDuckGoTextStyle( - body2.textStyle.copy( - fontWeight = FontWeight.Bold, - letterSpacing = 0.3.sp, - ), + val h4: TextStyle = TextStyle( + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.3.sp, + fontWeight = FontWeight.Medium, ), - val button: DuckDuckGoTextStyle = DuckDuckGoTextStyle( - TextStyle( - fontSize = 15.sp, - lineHeight = 20.sp, - fontWeight = FontWeight.Bold, - ), + val h5: TextStyle = TextStyle( + fontSize = 13.sp, + lineHeight = 16.sp, + fontWeight = FontWeight.Medium, ), - val caption: DuckDuckGoTextStyle = DuckDuckGoTextStyle( - TextStyle( - fontSize = 12.sp, - lineHeight = 16.sp, - letterSpacing = 0.2.sp, - ), + val body1: TextStyle = TextStyle( + fontSize = 16.sp, + lineHeight = 20.sp, + ), + + val body1Bold: TextStyle = body1.copy( + fontWeight = FontWeight.Bold, + ), + + val body1Mono: TextStyle = body1.copy( + fontFamily = RobotoMono, + ), + + val body2: TextStyle = TextStyle( + fontSize = 14.sp, + lineHeight = 18.sp, + letterSpacing = 0.2.sp, + ), + + val body2Bold: TextStyle = body2.copy( + fontWeight = FontWeight.Bold, + letterSpacing = 0.3.sp, + ), + + val button: TextStyle = TextStyle( + fontSize = 15.sp, + lineHeight = 20.sp, + fontWeight = FontWeight.Bold, + ), + + val caption: TextStyle = TextStyle( + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.2.sp, ), ) @@ -152,13 +120,3 @@ val Typography = DuckDuckGoTypography() val LocalDuckDuckGoTypography = staticCompositionLocalOf { error("No DuckDuckGoTypography provided") } - -@JvmInline -@Immutable -value class DuckDuckGoTextStyle internal constructor( - internal val textStyle: TextStyle, -) - -// Internal extension to extract TextStyle - only accessible within the design system -internal val DuckDuckGoTextStyle.asTextStyle: TextStyle - get() = textStyle diff --git a/lint-rules/src/main/java/com/duckduckgo/lint/ui/DaxTextColorUsageDetector.kt b/lint-rules/src/main/java/com/duckduckgo/lint/ui/DaxTextColorUsageDetector.kt index 172747c09b09..fd5bf1a73960 100644 --- a/lint-rules/src/main/java/com/duckduckgo/lint/ui/DaxTextColorUsageDetector.kt +++ b/lint-rules/src/main/java/com/duckduckgo/lint/ui/DaxTextColorUsageDetector.kt @@ -64,13 +64,13 @@ class DaxTextColorUsageDetector : Detector(), SourceCodeScanner { private fun isFromDuckDuckGoTextColors(argument: org.jetbrains.uast.UExpression): Boolean { val source = argument.sourcePsi?.text ?: return false - // Check if the source contains DuckDuckGoTheme.textColors or theme.textColors + // Check if the source contains DuckDuckGoTheme.colors.text or theme.colors.text // This covers cases like: - // - DuckDuckGoTheme.textColors.primary - // - theme.textColors.secondary - return source.contains("DuckDuckGoTheme.textColors") || - source.contains("theme.textColors") || - source.contains(".textColors.") + // - DuckDuckGoTheme.colors.text.primary + // - theme.colors.text.secondary + return source.contains("DuckDuckGoTheme.colors.text") || + source.contains("theme.colors.text") || + source.contains(".colors.text") } private fun reportInvalidColorUsage(colorArgument: org.jetbrains.uast.UExpression) { @@ -88,11 +88,11 @@ class DaxTextColorUsageDetector : Detector(), SourceCodeScanner { id = "InvalidDaxTextColorUsage", briefDescription = "DaxText color parameter should use DuckDuckGoTheme.textColors", explanation = """ - Use DuckDuckGoTheme.textColors instead of arbitrary Color values to maintain design system consistency and theme support. + Use DuckDuckGoTheme.colors.text instead of arbitrary Color values to maintain design system consistency and theme support. Examples: - • DuckDuckGoTheme.textColors.primary - • DuckDuckGoTheme.textColors.secondary + • DuckDuckGoTheme.colors.text.primary + • DuckDuckGoTheme.colors.text.secondary For one-off cases requiring custom colors, use good judgement or consider raising it in the Android Design System AOR. """.trimIndent(), diff --git a/lint-rules/src/test/java/com/duckduckgo/lint/ui/DaxTextColorUsageDetectorTest.kt b/lint-rules/src/test/java/com/duckduckgo/lint/ui/DaxTextColorUsageDetectorTest.kt index 6d4eb9b65dcd..6c0fc8218c88 100644 --- a/lint-rules/src/test/java/com/duckduckgo/lint/ui/DaxTextColorUsageDetectorTest.kt +++ b/lint-rules/src/test/java/com/duckduckgo/lint/ui/DaxTextColorUsageDetectorTest.kt @@ -54,6 +54,10 @@ class DaxTextColorUsageDetectorTest { import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color + data class DuckDuckGoColors( + val text: DuckDuckGoTextColors + ) + data class DuckDuckGoTextColors( val primary: Color, val secondary: Color, @@ -61,12 +65,14 @@ class DaxTextColorUsageDetectorTest { ) object DuckDuckGoTheme { - val textColors: DuckDuckGoTextColors + val colors: DuckDuckGoColors @Composable - get() = DuckDuckGoTextColors( - primary = Color(0xFF000000), - secondary = Color(0xFF666666), - tertiary = Color(0xFF999999) + get() = DuckDuckGoColors( + text = DuckDuckGoTextColors( + primary = Color(0xFF000000), + secondary = Color(0xFF666666), + tertiary = Color(0xFF999999) + ) ) } """.trimIndent() @@ -108,7 +114,7 @@ class DaxTextColorUsageDetectorTest { fun TestScreen() { DaxText( text = "Hello", - color = DuckDuckGoTheme.textColors.primary + color = DuckDuckGoTheme.colors.text.primary ) } """.trimIndent() @@ -141,7 +147,7 @@ class DaxTextColorUsageDetectorTest { val theme = DuckDuckGoTheme DaxText( text = "Hello", - color = theme.textColors.secondary + color = theme.colors.text.secondary ) } """.trimIndent() @@ -214,11 +220,11 @@ class DaxTextColorUsageDetectorTest { .run() .expect( """ - src/com/example/test/test.kt:11: Warning: Use DuckDuckGoTheme.textColors instead of arbitrary Color values to maintain design system consistency and theme support. + src/com/example/test/test.kt:11: Warning: Use DuckDuckGoTheme.colors.text instead of arbitrary Color values to maintain design system consistency and theme support. Examples: - • DuckDuckGoTheme.textColors.primary - • DuckDuckGoTheme.textColors.secondary + • DuckDuckGoTheme.colors.text.primary + • DuckDuckGoTheme.colors.text.secondary For one-off cases requiring custom colors, use good judgement or consider raising it in the Android Design System AOR. [InvalidDaxTextColorUsage] color = Color.Red @@ -258,11 +264,11 @@ class DaxTextColorUsageDetectorTest { .run() .expect( """ - src/com/example/test/test.kt:11: Warning: Use DuckDuckGoTheme.textColors instead of arbitrary Color values to maintain design system consistency and theme support. + src/com/example/test/test.kt:11: Warning: Use DuckDuckGoTheme.colors.text instead of arbitrary Color values to maintain design system consistency and theme support. Examples: - • DuckDuckGoTheme.textColors.primary - • DuckDuckGoTheme.textColors.secondary + • DuckDuckGoTheme.colors.text.primary + • DuckDuckGoTheme.colors.text.secondary For one-off cases requiring custom colors, use good judgement or consider raising it in the Android Design System AOR. [InvalidDaxTextColorUsage] color = Color(0xFF123456) @@ -289,7 +295,7 @@ class DaxTextColorUsageDetectorTest { fun TestScreen() { DaxText( text = "Valid", - color = DuckDuckGoTheme.textColors.primary + color = DuckDuckGoTheme.colors.text.primary ) DaxText( @@ -299,7 +305,7 @@ class DaxTextColorUsageDetectorTest { DaxText( text = "Also Valid", - color = DuckDuckGoTheme.textColors.secondary + color = DuckDuckGoTheme.colors.text.secondary ) DaxText( @@ -319,20 +325,20 @@ class DaxTextColorUsageDetectorTest { .run() .expect( """ - src/com/example/test/test.kt:17: Warning: Use DuckDuckGoTheme.textColors instead of arbitrary Color values to maintain design system consistency and theme support. + src/com/example/test/test.kt:17: Warning: Use DuckDuckGoTheme.colors.text instead of arbitrary Color values to maintain design system consistency and theme support. Examples: - • DuckDuckGoTheme.textColors.primary - • DuckDuckGoTheme.textColors.secondary + • DuckDuckGoTheme.colors.text.primary + • DuckDuckGoTheme.colors.text.secondary For one-off cases requiring custom colors, use good judgement or consider raising it in the Android Design System AOR. [InvalidDaxTextColorUsage] color = Color.Blue ~~~~~~~~~~ - src/com/example/test/test.kt:27: Warning: Use DuckDuckGoTheme.textColors instead of arbitrary Color values to maintain design system consistency and theme support. + src/com/example/test/test.kt:27: Warning: Use DuckDuckGoTheme.colors.text instead of arbitrary Color values to maintain design system consistency and theme support. Examples: - • DuckDuckGoTheme.textColors.primary - • DuckDuckGoTheme.textColors.secondary + • DuckDuckGoTheme.colors.text.primary + • DuckDuckGoTheme.colors.text.secondary For one-off cases requiring custom colors, use good judgement or consider raising it in the Android Design System AOR. [InvalidDaxTextColorUsage] color = Color(0xFFFF0000)