diff --git a/AGENTS.md b/AGENTS.md index 2b3d2f4..b7c77f7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,7 @@ This project houses **Stream Android Core**, the shared runtime used by Stream C ## Project structure - `stream-android-core/` – main library (models, socket/session, token handling, processors, batching, retry, queueing) +- `stream-android-core-ui/` – shared design tokens (`StreamDesign`, `StreamTokens`) as immutable Compose classes, consumed by the Chat and Video Compose themes; no dependency on `stream-android-core` - `stream-android-core-annotations/` – annotations + KSP helpers consumed by core - `stream-android-core-lint/` – lint checks shipped with the library - `app/` – demo/debug client used for manual verification diff --git a/README.md b/README.md index 08fbb89..25326ae 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,13 @@ Rather than duplicating infrastructure code across multiple SDKs, Stream Android └─────────────────────────────────────────────────────────┘ ``` +### Modules + +| Artifact | Contents | +| --- | --- | +| `io.getstream:stream-android-core` | The runtime described in this README: connectivity, tokens, state, reliability. | +| `io.getstream:stream-android-core-ui` | The shared design tokens (`StreamDesign`, `StreamTokens`) as immutable Compose classes, consumed by the Chat and Video Compose themes. It does not depend on `stream-android-core`. | + ## Overview **Stream Android Core** provides shared primitives and infrastructure for Stream's Android SDKs: diff --git a/build.gradle.kts b/build.gradle.kts index df8ace3..d2fe325 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -41,7 +41,7 @@ streamProject { } coverage { - includedModules = setOf("stream-android-core") + includedModules = setOf("stream-android-core", "stream-android-core-ui") } publishing { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5e3330a..78180e1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,6 +51,9 @@ androidx-activity-compose = { group = "androidx.activity", name = "activity-comp androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-text = { group = "androidx.compose.ui", name = "ui-text" } +androidx-ui-unit = { group = "androidx.compose.ui", name = "ui-unit" } +androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 59b0937..8caea61 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,3 +44,4 @@ rootProject.name = "stream-android-core" include(":app") include(":stream-android-core-lint") include(":stream-android-core") +include(":stream-android-core-ui") diff --git a/stream-android-core-ui/api/stream-android-core-ui.api b/stream-android-core-ui/api/stream-android-core-ui.api new file mode 100644 index 0000000..6d7f5a8 --- /dev/null +++ b/stream-android-core-ui/api/stream-android-core-ui.api @@ -0,0 +1,477 @@ +public final class io/getstream/android/core/ui/design/StreamDesign { + public static final field $stable I + public static final field INSTANCE Lio/getstream/android/core/ui/design/StreamDesign; +} + +public final class io/getstream/android/core/ui/design/StreamDesign$ChromeScale { + public static final field $stable I + public static final field Companion Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale$Companion; + public synthetic fun (JJJJJJJJJJJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-0d7_KjU ()J + public final fun component10-0d7_KjU ()J + public final fun component11-0d7_KjU ()J + public final fun component12-0d7_KjU ()J + public final fun component13-0d7_KjU ()J + public final fun component2-0d7_KjU ()J + public final fun component3-0d7_KjU ()J + public final fun component4-0d7_KjU ()J + public final fun component5-0d7_KjU ()J + public final fun component6-0d7_KjU ()J + public final fun component7-0d7_KjU ()J + public final fun component8-0d7_KjU ()J + public final fun component9-0d7_KjU ()J + public final fun copy-daRQuJA (JJJJJJJJJJJJJ)Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale; + public static synthetic fun copy-daRQuJA$default (Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale;JJJJJJJJJJJJJILjava/lang/Object;)Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale; + public fun equals (Ljava/lang/Object;)Z + public final fun getS0-0d7_KjU ()J + public final fun getS100-0d7_KjU ()J + public final fun getS1000-0d7_KjU ()J + public final fun getS150-0d7_KjU ()J + public final fun getS200-0d7_KjU ()J + public final fun getS300-0d7_KjU ()J + public final fun getS400-0d7_KjU ()J + public final fun getS50-0d7_KjU ()J + public final fun getS500-0d7_KjU ()J + public final fun getS600-0d7_KjU ()J + public final fun getS700-0d7_KjU ()J + public final fun getS800-0d7_KjU ()J + public final fun getS900-0d7_KjU ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/core/ui/design/StreamDesign$ChromeScale$Companion { + public final fun defaultDark ()Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale; + public final fun defaultLight ()Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale; +} + +public final class io/getstream/android/core/ui/design/StreamDesign$ColorScale { + public static final field $stable I + public static final field Companion Lio/getstream/android/core/ui/design/StreamDesign$ColorScale$Companion; + public synthetic fun (JJJJJJJJJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1-0d7_KjU ()J + public final fun component10-0d7_KjU ()J + public final fun component11-0d7_KjU ()J + public final fun component2-0d7_KjU ()J + public final fun component3-0d7_KjU ()J + public final fun component4-0d7_KjU ()J + public final fun component5-0d7_KjU ()J + public final fun component6-0d7_KjU ()J + public final fun component7-0d7_KjU ()J + public final fun component8-0d7_KjU ()J + public final fun component9-0d7_KjU ()J + public final fun copy-KpyCeHw (JJJJJJJJJJJ)Lio/getstream/android/core/ui/design/StreamDesign$ColorScale; + public static synthetic fun copy-KpyCeHw$default (Lio/getstream/android/core/ui/design/StreamDesign$ColorScale;JJJJJJJJJJJILjava/lang/Object;)Lio/getstream/android/core/ui/design/StreamDesign$ColorScale; + public fun equals (Ljava/lang/Object;)Z + public final fun getS100-0d7_KjU ()J + public final fun getS150-0d7_KjU ()J + public final fun getS200-0d7_KjU ()J + public final fun getS300-0d7_KjU ()J + public final fun getS400-0d7_KjU ()J + public final fun getS50-0d7_KjU ()J + public final fun getS500-0d7_KjU ()J + public final fun getS600-0d7_KjU ()J + public final fun getS700-0d7_KjU ()J + public final fun getS800-0d7_KjU ()J + public final fun getS900-0d7_KjU ()J + public fun hashCode ()I + public final fun inverted ()Lio/getstream/android/core/ui/design/StreamDesign$ColorScale; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/core/ui/design/StreamDesign$ColorScale$Companion { + public final fun defaultDark ()Lio/getstream/android/core/ui/design/StreamDesign$ColorScale; + public final fun defaultLight ()Lio/getstream/android/core/ui/design/StreamDesign$ColorScale; + public final fun from-8_81llA (J)Lio/getstream/android/core/ui/design/StreamDesign$ColorScale; +} + +public final class io/getstream/android/core/ui/design/StreamDesign$Colors { + public static final field $stable I + public static final field Companion Lio/getstream/android/core/ui/design/StreamDesign$Colors$Companion; + public synthetic fun (Lio/getstream/android/core/ui/design/StreamDesign$ColorScale;Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale;JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/android/core/ui/design/StreamDesign$ColorScale; + public final fun component10-0d7_KjU ()J + public final fun component11-0d7_KjU ()J + public final fun component12-0d7_KjU ()J + public final fun component13-0d7_KjU ()J + public final fun component14-0d7_KjU ()J + public final fun component15-0d7_KjU ()J + public final fun component16-0d7_KjU ()J + public final fun component17-0d7_KjU ()J + public final fun component18-0d7_KjU ()J + public final fun component19-0d7_KjU ()J + public final fun component2 ()Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale; + public final fun component20-0d7_KjU ()J + public final fun component21-0d7_KjU ()J + public final fun component22-0d7_KjU ()J + public final fun component23-0d7_KjU ()J + public final fun component24-0d7_KjU ()J + public final fun component25-0d7_KjU ()J + public final fun component26-0d7_KjU ()J + public final fun component27-0d7_KjU ()J + public final fun component28-0d7_KjU ()J + public final fun component29-0d7_KjU ()J + public final fun component3-0d7_KjU ()J + public final fun component30-0d7_KjU ()J + public final fun component31-0d7_KjU ()J + public final fun component32-0d7_KjU ()J + public final fun component33-0d7_KjU ()J + public final fun component34-0d7_KjU ()J + public final fun component35-0d7_KjU ()J + public final fun component36-0d7_KjU ()J + public final fun component37-0d7_KjU ()J + public final fun component38-0d7_KjU ()J + public final fun component39-0d7_KjU ()J + public final fun component4-0d7_KjU ()J + public final fun component40-0d7_KjU ()J + public final fun component41-0d7_KjU ()J + public final fun component42-0d7_KjU ()J + public final fun component43-0d7_KjU ()J + public final fun component44-0d7_KjU ()J + public final fun component45-0d7_KjU ()J + public final fun component46-0d7_KjU ()J + public final fun component47-0d7_KjU ()J + public final fun component48-0d7_KjU ()J + public final fun component49-0d7_KjU ()J + public final fun component5-0d7_KjU ()J + public final fun component50-0d7_KjU ()J + public final fun component51-0d7_KjU ()J + public final fun component52-0d7_KjU ()J + public final fun component53-0d7_KjU ()J + public final fun component54-0d7_KjU ()J + public final fun component55-0d7_KjU ()J + public final fun component56-0d7_KjU ()J + public final fun component57-0d7_KjU ()J + public final fun component58-0d7_KjU ()J + public final fun component59-0d7_KjU ()J + public final fun component6-0d7_KjU ()J + public final fun component60-0d7_KjU ()J + public final fun component61-0d7_KjU ()J + public final fun component62-0d7_KjU ()J + public final fun component63-0d7_KjU ()J + public final fun component64-0d7_KjU ()J + public final fun component7-0d7_KjU ()J + public final fun component8-0d7_KjU ()J + public final fun component9-0d7_KjU ()J + public final fun copy-xoxRo9Q (Lio/getstream/android/core/ui/design/StreamDesign$ColorScale;Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale;JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ)Lio/getstream/android/core/ui/design/StreamDesign$Colors; + public static synthetic fun copy-xoxRo9Q$default (Lio/getstream/android/core/ui/design/StreamDesign$Colors;Lio/getstream/android/core/ui/design/StreamDesign$ColorScale;Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale;JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIILjava/lang/Object;)Lio/getstream/android/core/ui/design/StreamDesign$Colors; + public fun equals (Ljava/lang/Object;)Z + public final fun getAccentError-0d7_KjU ()J + public final fun getAccentNeutral-0d7_KjU ()J + public final fun getAccentPrimary-0d7_KjU ()J + public final fun getAccentSuccess-0d7_KjU ()J + public final fun getAccentWarning-0d7_KjU ()J + public final fun getAvatarBgDefault-0d7_KjU ()J + public final fun getAvatarBgPlaceholder-0d7_KjU ()J + public final fun getAvatarPresenceBgOffline-0d7_KjU ()J + public final fun getAvatarPresenceBgOnline-0d7_KjU ()J + public final fun getAvatarPresenceBorder-0d7_KjU ()J + public final fun getAvatarTextDefault-0d7_KjU ()J + public final fun getAvatarTextPlaceholder-0d7_KjU ()J + public final fun getBackgroundCoreApp-0d7_KjU ()J + public final fun getBackgroundCoreElevation0-0d7_KjU ()J + public final fun getBackgroundCoreElevation1-0d7_KjU ()J + public final fun getBackgroundCoreElevation2-0d7_KjU ()J + public final fun getBackgroundCoreElevation3-0d7_KjU ()J + public final fun getBackgroundCoreHighlight-0d7_KjU ()J + public final fun getBackgroundCoreInverse-0d7_KjU ()J + public final fun getBackgroundCoreOnAccent-0d7_KjU ()J + public final fun getBackgroundCoreOnElevation-0d7_KjU ()J + public final fun getBackgroundCoreOverlayDark-0d7_KjU ()J + public final fun getBackgroundCoreOverlayDarkStrong-0d7_KjU ()J + public final fun getBackgroundCoreOverlayLight-0d7_KjU ()J + public final fun getBackgroundCoreScrim-0d7_KjU ()J + public final fun getBackgroundCoreSurfaceCard-0d7_KjU ()J + public final fun getBackgroundCoreSurfaceDefault-0d7_KjU ()J + public final fun getBackgroundCoreSurfaceStrong-0d7_KjU ()J + public final fun getBackgroundCoreSurfaceSubtle-0d7_KjU ()J + public final fun getBackgroundUtilityDisabled-0d7_KjU ()J + public final fun getBackgroundUtilityHover-0d7_KjU ()J + public final fun getBackgroundUtilityPressed-0d7_KjU ()J + public final fun getBackgroundUtilitySelected-0d7_KjU ()J + public final fun getBackgroundUtilitySkeletonLoadingBase-0d7_KjU ()J + public final fun getBackgroundUtilitySkeletonLoadingHighlight-0d7_KjU ()J + public final fun getBadgeBgDefault-0d7_KjU ()J + public final fun getBadgeBgError-0d7_KjU ()J + public final fun getBadgeBgInverse-0d7_KjU ()J + public final fun getBadgeBgNeutral-0d7_KjU ()J + public final fun getBadgeBgOverlay-0d7_KjU ()J + public final fun getBadgeBgPrimary-0d7_KjU ()J + public final fun getBadgeBorder-0d7_KjU ()J + public final fun getBadgeText-0d7_KjU ()J + public final fun getBadgeTextOnAccent-0d7_KjU ()J + public final fun getBadgeTextOnInverse-0d7_KjU ()J + public final fun getBorderCoreDefault-0d7_KjU ()J + public final fun getBorderCoreOnAccent-0d7_KjU ()J + public final fun getBorderCoreOnElevation-0d7_KjU ()J + public final fun getBorderCoreOnInverse-0d7_KjU ()J + public final fun getBorderCoreOnSurface-0d7_KjU ()J + public final fun getBorderCoreOpacityStrong-0d7_KjU ()J + public final fun getBorderCoreOpacitySubtle-0d7_KjU ()J + public final fun getBorderCoreStrong-0d7_KjU ()J + public final fun getBorderCoreSubtle-0d7_KjU ()J + public final fun getBorderUtilityActive-0d7_KjU ()J + public final fun getBorderUtilityDisabled-0d7_KjU ()J + public final fun getBorderUtilityDisabledOnSurface-0d7_KjU ()J + public final fun getBorderUtilityError-0d7_KjU ()J + public final fun getBorderUtilityFocused-0d7_KjU ()J + public final fun getBorderUtilityHover-0d7_KjU ()J + public final fun getBorderUtilityPressed-0d7_KjU ()J + public final fun getBorderUtilitySelected-0d7_KjU ()J + public final fun getBorderUtilitySuccess-0d7_KjU ()J + public final fun getBorderUtilityWarning-0d7_KjU ()J + public final fun getBrand ()Lio/getstream/android/core/ui/design/StreamDesign$ColorScale; + public final fun getButtonDestructiveBg-0d7_KjU ()J + public final fun getButtonDestructiveBgLiquidGlass-0d7_KjU ()J + public final fun getButtonDestructiveBorder-0d7_KjU ()J + public final fun getButtonDestructiveBorderOnDark-0d7_KjU ()J + public final fun getButtonDestructiveText-0d7_KjU ()J + public final fun getButtonDestructiveTextOnAccent-0d7_KjU ()J + public final fun getButtonDestructiveTextOnDark-0d7_KjU ()J + public final fun getButtonPrimaryBg-0d7_KjU ()J + public final fun getButtonPrimaryBgLiquidGlass-0d7_KjU ()J + public final fun getButtonPrimaryBorder-0d7_KjU ()J + public final fun getButtonPrimaryBorderOnDark-0d7_KjU ()J + public final fun getButtonPrimaryText-0d7_KjU ()J + public final fun getButtonPrimaryTextOnAccent-0d7_KjU ()J + public final fun getButtonPrimaryTextOnDark-0d7_KjU ()J + public final fun getButtonSecondaryBg-0d7_KjU ()J + public final fun getButtonSecondaryBgLiquidGlass-0d7_KjU ()J + public final fun getButtonSecondaryBorder-0d7_KjU ()J + public final fun getButtonSecondaryBorderOnDark-0d7_KjU ()J + public final fun getButtonSecondaryText-0d7_KjU ()J + public final fun getButtonSecondaryTextOnAccent-0d7_KjU ()J + public final fun getButtonSecondaryTextOnDark-0d7_KjU ()J + public final fun getChrome ()Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale; + public final fun getControlCheckboxBg-0d7_KjU ()J + public final fun getControlCheckboxBgSelected-0d7_KjU ()J + public final fun getControlCheckboxBorder-0d7_KjU ()J + public final fun getControlCheckboxIcon-0d7_KjU ()J + public final fun getControlChipBorder-0d7_KjU ()J + public final fun getControlChipText-0d7_KjU ()J + public final fun getControlProgressBarFill-0d7_KjU ()J + public final fun getControlProgressBarFillAudio-0d7_KjU ()J + public final fun getControlProgressBarTrack-0d7_KjU ()J + public final fun getControlRadioButtonBg-0d7_KjU ()J + public final fun getControlRadioButtonBgSelected-0d7_KjU ()J + public final fun getControlRadioButtonBorder-0d7_KjU ()J + public final fun getControlRadioButtonIndicator-0d7_KjU ()J + public final fun getControlRadioCheckBg-0d7_KjU ()J + public final fun getControlRadioCheckBgSelected-0d7_KjU ()J + public final fun getControlRadioCheckBorder-0d7_KjU ()J + public final fun getControlRadioCheckIcon-0d7_KjU ()J + public final fun getControlToggleSwitchBg-0d7_KjU ()J + public final fun getControlToggleSwitchBgDisabled-0d7_KjU ()J + public final fun getControlToggleSwitchBgSelected-0d7_KjU ()J + public final fun getControlToggleSwitchKnob-0d7_KjU ()J + public final fun getInputTextDefault-0d7_KjU ()J + public final fun getInputTextDisabled-0d7_KjU ()J + public final fun getInputTextIcon-0d7_KjU ()J + public final fun getInputTextIconActive-0d7_KjU ()J + public final fun getInputTextPlaceholder-0d7_KjU ()J + public final fun getLabelBgNeutral-0d7_KjU ()J + public final fun getLabelBgPrimary-0d7_KjU ()J + public final fun getLabelTextNeutral-0d7_KjU ()J + public final fun getLabelTextPrimary-0d7_KjU ()J + public final fun getSystemBgBlur-0d7_KjU ()J + public final fun getSystemCaret-0d7_KjU ()J + public final fun getSystemScrollbar-0d7_KjU ()J + public final fun getSystemText-0d7_KjU ()J + public final fun getTabIndicator-0d7_KjU ()J + public final fun getTabText-0d7_KjU ()J + public final fun getTabTextSelected-0d7_KjU ()J + public final fun getTabTrack-0d7_KjU ()J + public final fun getTextDisabled-0d7_KjU ()J + public final fun getTextLink-0d7_KjU ()J + public final fun getTextOnAccent-0d7_KjU ()J + public final fun getTextOnInverse-0d7_KjU ()J + public final fun getTextPrimary-0d7_KjU ()J + public final fun getTextSecondary-0d7_KjU ()J + public final fun getTextTertiary-0d7_KjU ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/core/ui/design/StreamDesign$Colors$Companion { + public final fun default (Lio/getstream/android/core/ui/design/StreamDesign$ColorScale;Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale;)Lio/getstream/android/core/ui/design/StreamDesign$Colors; + public static synthetic fun default$default (Lio/getstream/android/core/ui/design/StreamDesign$Colors$Companion;Lio/getstream/android/core/ui/design/StreamDesign$ColorScale;Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale;ILjava/lang/Object;)Lio/getstream/android/core/ui/design/StreamDesign$Colors; + public final fun defaultDark (Lio/getstream/android/core/ui/design/StreamDesign$ColorScale;Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale;)Lio/getstream/android/core/ui/design/StreamDesign$Colors; + public static synthetic fun defaultDark$default (Lio/getstream/android/core/ui/design/StreamDesign$Colors$Companion;Lio/getstream/android/core/ui/design/StreamDesign$ColorScale;Lio/getstream/android/core/ui/design/StreamDesign$ChromeScale;ILjava/lang/Object;)Lio/getstream/android/core/ui/design/StreamDesign$Colors; +} + +public final class io/getstream/android/core/ui/design/StreamDesign$Typography { + public static final field $stable I + public static final field Companion Lio/getstream/android/core/ui/design/StreamDesign$Typography$Companion; + public fun (Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;)V + public final fun component1 ()Landroidx/compose/ui/text/TextStyle; + public final fun component10 ()Landroidx/compose/ui/text/TextStyle; + public final fun component11 ()Landroidx/compose/ui/text/TextStyle; + public final fun component12 ()Landroidx/compose/ui/text/TextStyle; + public final fun component13 ()Landroidx/compose/ui/text/TextStyle; + public final fun component14 ()Landroidx/compose/ui/text/TextStyle; + public final fun component15 ()Landroidx/compose/ui/text/TextStyle; + public final fun component16 ()Landroidx/compose/ui/text/TextStyle; + public final fun component17 ()Landroidx/compose/ui/text/TextStyle; + public final fun component18 ()Landroidx/compose/ui/text/TextStyle; + public final fun component19 ()Landroidx/compose/ui/text/TextStyle; + public final fun component2 ()Landroidx/compose/ui/text/TextStyle; + public final fun component20 ()Landroidx/compose/ui/text/TextStyle; + public final fun component3 ()Landroidx/compose/ui/text/TextStyle; + public final fun component4 ()Landroidx/compose/ui/text/TextStyle; + public final fun component5 ()Landroidx/compose/ui/text/TextStyle; + public final fun component6 ()Landroidx/compose/ui/text/TextStyle; + public final fun component7 ()Landroidx/compose/ui/text/TextStyle; + public final fun component8 ()Landroidx/compose/ui/text/TextStyle; + public final fun component9 ()Landroidx/compose/ui/text/TextStyle; + public final fun copy (Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;)Lio/getstream/android/core/ui/design/StreamDesign$Typography; + public static synthetic fun copy$default (Lio/getstream/android/core/ui/design/StreamDesign$Typography;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;ILjava/lang/Object;)Lio/getstream/android/core/ui/design/StreamDesign$Typography; + public fun equals (Ljava/lang/Object;)Z + public final fun getBodyDefault ()Landroidx/compose/ui/text/TextStyle; + public final fun getBodyEmphasis ()Landroidx/compose/ui/text/TextStyle; + public final fun getBodyLink ()Landroidx/compose/ui/text/TextStyle; + public final fun getBodyLinkEmphasis ()Landroidx/compose/ui/text/TextStyle; + public final fun getCaptionDefault ()Landroidx/compose/ui/text/TextStyle; + public final fun getCaptionEmphasis ()Landroidx/compose/ui/text/TextStyle; + public final fun getCaptionLink ()Landroidx/compose/ui/text/TextStyle; + public final fun getCaptionLinkEmphasis ()Landroidx/compose/ui/text/TextStyle; + public final fun getHeadingExtraSmall ()Landroidx/compose/ui/text/TextStyle; + public final fun getHeadingLarge ()Landroidx/compose/ui/text/TextStyle; + public final fun getHeadingMedium ()Landroidx/compose/ui/text/TextStyle; + public final fun getHeadingSmall ()Landroidx/compose/ui/text/TextStyle; + public final fun getMetadataDefault ()Landroidx/compose/ui/text/TextStyle; + public final fun getMetadataEmphasis ()Landroidx/compose/ui/text/TextStyle; + public final fun getMetadataLink ()Landroidx/compose/ui/text/TextStyle; + public final fun getMetadataLinkEmphasis ()Landroidx/compose/ui/text/TextStyle; + public final fun getNumericExtraLarge ()Landroidx/compose/ui/text/TextStyle; + public final fun getNumericLarge ()Landroidx/compose/ui/text/TextStyle; + public final fun getNumericMedium ()Landroidx/compose/ui/text/TextStyle; + public final fun getNumericSmall ()Landroidx/compose/ui/text/TextStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/android/core/ui/design/StreamDesign$Typography$Companion { + public final fun default (Landroidx/compose/ui/text/font/FontFamily;)Lio/getstream/android/core/ui/design/StreamDesign$Typography; + public static synthetic fun default$default (Lio/getstream/android/core/ui/design/StreamDesign$Typography$Companion;Landroidx/compose/ui/text/font/FontFamily;ILjava/lang/Object;)Lio/getstream/android/core/ui/design/StreamDesign$Typography; +} + +public final class io/getstream/android/core/ui/design/StreamTokens { + public static final field $stable I + public static final field INSTANCE Lio/getstream/android/core/ui/design/StreamTokens; + public final fun getButtonHitTargetMinHeight-D9Ej5fM ()F + public final fun getButtonHitTargetMinWidth-D9Ej5fM ()F + public final fun getButtonPaddingXIconOnlyLg-D9Ej5fM ()F + public final fun getButtonPaddingXIconOnlyMd-D9Ej5fM ()F + public final fun getButtonPaddingXIconOnlySm-D9Ej5fM ()F + public final fun getButtonPaddingXIconOnlyXs-D9Ej5fM ()F + public final fun getButtonPaddingXWithLabelLg-D9Ej5fM ()F + public final fun getButtonPaddingXWithLabelMd-D9Ej5fM ()F + public final fun getButtonPaddingXWithLabelSm-D9Ej5fM ()F + public final fun getButtonPaddingXWithLabelXs-D9Ej5fM ()F + public final fun getButtonPaddingYLg-D9Ej5fM ()F + public final fun getButtonPaddingYMd-D9Ej5fM ()F + public final fun getButtonPaddingYSm-D9Ej5fM ()F + public final fun getButtonPaddingYXs-D9Ej5fM ()F + public final fun getButtonRadiusFull-D9Ej5fM ()F + public final fun getButtonRadiusLg-D9Ej5fM ()F + public final fun getButtonRadiusMd-D9Ej5fM ()F + public final fun getButtonRadiusSm-D9Ej5fM ()F + public final fun getButtonVisualHeightLg-D9Ej5fM ()F + public final fun getButtonVisualHeightMd-D9Ej5fM ()F + public final fun getButtonVisualHeightSm-D9Ej5fM ()F + public final fun getButtonVisualHeightXs-D9Ej5fM ()F + public final fun getDeviceRadius-D9Ej5fM ()F + public final fun getDeviceSafeAreaBottom-D9Ej5fM ()F + public final fun getDeviceSafeAreaTop-D9Ej5fM ()F + public final fun getEmoji2xl-XSAIIZE ()J + public final fun getEmojiLg-XSAIIZE ()J + public final fun getEmojiMd-XSAIIZE ()J + public final fun getEmojiSm-XSAIIZE ()J + public final fun getEmojiXl-XSAIIZE ()J + public final fun getFontSize2xl-XSAIIZE ()J + public final fun getFontSize2xs-XSAIIZE ()J + public final fun getFontSizeLg-XSAIIZE ()J + public final fun getFontSizeMd-XSAIIZE ()J + public final fun getFontSizeMicro-XSAIIZE ()J + public final fun getFontSizeSm-XSAIIZE ()J + public final fun getFontSizeXl-XSAIIZE ()J + public final fun getFontSizeXs-XSAIIZE ()J + public final fun getFontWeightBold ()Landroidx/compose/ui/text/font/FontWeight; + public final fun getFontWeightMedium ()Landroidx/compose/ui/text/font/FontWeight; + public final fun getFontWeightRegular ()Landroidx/compose/ui/text/font/FontWeight; + public final fun getFontWeightSemiBold ()Landroidx/compose/ui/text/font/FontWeight; + public final fun getIconSizeLg-D9Ej5fM ()F + public final fun getIconSizeMd-D9Ej5fM ()F + public final fun getIconSizeSm-D9Ej5fM ()F + public final fun getIconSizeXs-D9Ej5fM ()F + public final fun getIconStrokeDefault-D9Ej5fM ()F + public final fun getIconStrokeEmphasis-D9Ej5fM ()F + public final fun getIconStrokeSubtle-D9Ej5fM ()F + public final fun getInputRadiusOptionCard-D9Ej5fM ()F + public final fun getInputRadiusSearchInput-D9Ej5fM ()F + public final fun getInputRadiusSelectInput-D9Ej5fM ()F + public final fun getInputRadiusTextInput-D9Ej5fM ()F + public final fun getLineHeightNormal-XSAIIZE ()J + public final fun getLineHeightRelaxed-XSAIIZE ()J + public final fun getLineHeightTight-XSAIIZE ()J + public final fun getRadius2xl-D9Ej5fM ()F + public final fun getRadius2xs-D9Ej5fM ()F + public final fun getRadius3xl-D9Ej5fM ()F + public final fun getRadius4xl-D9Ej5fM ()F + public final fun getRadiusLg-D9Ej5fM ()F + public final fun getRadiusMax-D9Ej5fM ()F + public final fun getRadiusMd-D9Ej5fM ()F + public final fun getRadiusNone-D9Ej5fM ()F + public final fun getRadiusSm-D9Ej5fM ()F + public final fun getRadiusXl-D9Ej5fM ()F + public final fun getRadiusXs-D9Ej5fM ()F + public final fun getSize10-D9Ej5fM ()F + public final fun getSize12-D9Ej5fM ()F + public final fun getSize128-D9Ej5fM ()F + public final fun getSize13-D9Ej5fM ()F + public final fun getSize14-D9Ej5fM ()F + public final fun getSize144-D9Ej5fM ()F + public final fun getSize15-D9Ej5fM ()F + public final fun getSize16-D9Ej5fM ()F + public final fun getSize17-D9Ej5fM ()F + public final fun getSize18-D9Ej5fM ()F + public final fun getSize2-D9Ej5fM ()F + public final fun getSize20-D9Ej5fM ()F + public final fun getSize208-D9Ej5fM ()F + public final fun getSize22-D9Ej5fM ()F + public final fun getSize24-D9Ej5fM ()F + public final fun getSize240-D9Ej5fM ()F + public final fun getSize28-D9Ej5fM ()F + public final fun getSize32-D9Ej5fM ()F + public final fun getSize320-D9Ej5fM ()F + public final fun getSize4-D9Ej5fM ()F + public final fun getSize40-D9Ej5fM ()F + public final fun getSize48-D9Ej5fM ()F + public final fun getSize480-D9Ej5fM ()F + public final fun getSize56-D9Ej5fM ()F + public final fun getSize560-D9Ej5fM ()F + public final fun getSize6-D9Ej5fM ()F + public final fun getSize64-D9Ej5fM ()F + public final fun getSize640-D9Ej5fM ()F + public final fun getSize760-D9Ej5fM ()F + public final fun getSize8-D9Ej5fM ()F + public final fun getSize80-D9Ej5fM ()F + public final fun getSpacing2xl-D9Ej5fM ()F + public final fun getSpacing2xs-D9Ej5fM ()F + public final fun getSpacing3xl-D9Ej5fM ()F + public final fun getSpacing3xs-D9Ej5fM ()F + public final fun getSpacingLg-D9Ej5fM ()F + public final fun getSpacingMd-D9Ej5fM ()F + public final fun getSpacingNone-D9Ej5fM ()F + public final fun getSpacingSm-D9Ej5fM ()F + public final fun getSpacingXl-D9Ej5fM ()F + public final fun getSpacingXs-D9Ej5fM ()F + public final fun getStrokeW100-D9Ej5fM ()F + public final fun getStrokeW120-D9Ej5fM ()F + public final fun getStrokeW150-D9Ej5fM ()F + public final fun getStrokeW200-D9Ej5fM ()F + public final fun getStrokeW300-D9Ej5fM ()F + public final fun getStrokeW400-D9Ej5fM ()F + public final fun getStrokeW80-D9Ej5fM ()F +} + diff --git a/stream-android-core-ui/build.gradle.kts b/stream-android-core-ui/build.gradle.kts new file mode 100644 index 0000000..9f86bcc --- /dev/null +++ b/stream-android-core-ui/build.gradle.kts @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014-2025 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-core-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.stream.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.arturbosch.detekt) +} + +kotlin { + explicitApi() + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + freeCompilerArgs.add("-opt-in=io.getstream.android.core.annotations.StreamInternalApi") + } +} + +android { + namespace = "io.getstream.android.core.ui" + + buildFeatures { + compose = true + } + + lint { + abortOnError = true + warningsAsErrors = true + lintConfig = rootProject.file("lint.xml") + } +} + +dependencies { + // Linter + lintChecks(project(":stream-android-core-lint")) + + detektPlugins(libs.detekt.formatting) + + api(project(":stream-android-core-annotations")) + + // Compose. The token classes expose these types in their public API, so consumers get them + // transitively. The BOM is in the api scope so it also sets the version floor for consumers. + api(platform(libs.androidx.compose.bom)) + api(libs.androidx.compose.runtime) + api(libs.androidx.ui.graphics) + api(libs.androidx.ui.text) + api(libs.androidx.ui.unit) + + testImplementation(kotlin("test")) + testImplementation(libs.junit) +} diff --git a/stream-android-core-ui/src/main/java/io/getstream/android/core/ui/design/StreamDesign.kt b/stream-android-core-ui/src/main/java/io/getstream/android/core/ui/design/StreamDesign.kt new file mode 100644 index 0000000..7f7adb7 --- /dev/null +++ b/stream-android-core-ui/src/main/java/io/getstream/android/core/ui/design/StreamDesign.kt @@ -0,0 +1,1000 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-core-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("MagicNumber", "LongMethod", "LongParameterList") + +package io.getstream.android.core.ui.design + +import androidx.compose.runtime.Immutable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import io.getstream.android.core.annotations.StreamInternalApi +import io.getstream.android.core.annotations.StreamPublishedApi +import io.getstream.android.core.ui.design.StreamDesign.Colors.Companion.default +import io.getstream.android.core.ui.design.StreamDesign.Colors.Companion.defaultDark + +/** + * The Stream Design System namespace: the token layer shared by the Stream SDKs. + * - [ColorScale] is the brand (accent) color ramp. + * - [ChromeScale] is the chrome (neutral gray) color ramp. + * - [Colors] holds the semantic color tokens derived from the scales above. + * - [Typography] holds the text styles. + * + * Product-specific tokens live in each SDK and derive from these. + * + * Use through the SDK theme, for example `VideoTheme` or `ChatTheme`: + * ``` + * VideoTheme( + * colors = StreamDesign.Colors.default().copy(accentPrimary = ...), + * typography = StreamDesign.Typography.default(fontFamily = ...), + * ) { content() } + * ``` + */ +@StreamPublishedApi +public object StreamDesign { + + /** + * Semantic color tokens. Customize through [default] / [defaultDark] parameters or [copy]. + * + * @param brand The brand (accent) color scale. See [ColorScale]. + * @param chrome The chrome (neutral gray) color scale. See [ChromeScale]. + * @param accentPrimary The main brand color. Used for interactive elements, buttons, links, and + * primary actions. Override this to apply your brand color across the SDK. + * @param accentSuccess Indicates a positive or completed state. Used for confirmations, sent + * indicators, and success feedback. + * @param accentWarning Indicates a cautionary state that requires attention. Used for + * moderation flags and non-critical alerts. + * @param accentError Indicates a failure or destructive state. Used for failed messages, + * validation errors, and deletions. + * @param accentNeutral A mid-tone gray for de-emphasized UI elements. + * @param textPrimary Main body text. Used for message content, titles, and any text that + * carries primary meaning. + * @param textSecondary Supporting metadata text. Used for timestamps, subtitles, and secondary + * labels. + * @param textTertiary De-emphasized text. Used for hints, placeholders, and lowest-priority + * supporting information. + * @param textOnInverse Text on inverse-colored surfaces. Flips between light and dark to + * maintain legibility when the background inverts. + * @param textOnAccent Text on accent-colored surfaces. Stays white in both light and dark mode + * since the accent background doesn't invert. + * @param textDisabled Text for non-interactive or unavailable states. Communicates that an + * element can't be interacted with. + * @param textLink Hyperlinks and inline actions. Uses the brand color to signal interactivity + * within text content. + * @param backgroundCoreApp The outermost application background. Sits behind all surfaces and + * is generally not overridden directly. + * @param backgroundCoreElevation0 The base layer. White in light mode and black in dark mode, + * used as the reference point for the elevation scale. Steps above this gain depth in dark + * mode through progressively lighter backgrounds. + * @param backgroundCoreElevation1 Slightly raised surfaces. Used for content containers that + * sit directly on the base layer, such as the message list and channel list. + * @param backgroundCoreElevation2 Floating and modal surfaces. Used for popovers, dropdowns, + * dialogs, and any element that interrupts the content flow. + * @param backgroundCoreElevation3 Used for badge counts that float above other UI elements. + * @param backgroundCoreOnElevation Background for controls placed on an elevated surface. + * Matches the surface in light mode and lifts in dark mode so the control keeps its edge. + * @param backgroundCoreSurfaceDefault Background for sectioned content areas. Used for grouped + * containers, and distinct content regions. + * @param backgroundCoreSurfaceSubtle A slightly receded background. Used for secondary + * containers or to create soft visual separation. + * @param backgroundCoreSurfaceCard Background for contained, card-style elements. Matches the + * surface in light mode but lifts slightly in dark mode to maintain visual separation. + * @param backgroundCoreSurfaceStrong A more prominent background. Used for elements that need + * to stand out from the main surface. + * @param backgroundCoreInverse The opposite of the primary surface. Used for tooltips, + * snackbars, and high-contrast floating elements. + * @param backgroundCoreOnAccent Background for elements placed on an accent-colored surface. + * Ensures legibility against brand colors. + * @param backgroundCoreHighlight A tint for drawing attention to content. Used for highlights + * and pinned messages. + * @param backgroundCoreOverlayLight A light semi-transparent layer. Used to lighten surfaces + * and for hover states on dark backgrounds. + * @param backgroundCoreOverlayDark A dark semi-transparent layer. Used for image overlays. + * @param backgroundCoreScrim A heavy semi-transparent layer. Used behind sheets, drawers, and + * modals to separate them from content. + * @param backgroundCoreOverlayDarkStrong A stronger version of [backgroundCoreOverlayDark], + * with higher opacity. Used for image overlays. + * @param backgroundUtilityHover A semi-transparent overlay applied on pointer hover. Sits on + * top of any surface without replacing it. + * @param backgroundUtilityPressed A slightly stronger overlay applied during an active press or + * tap. Provides tactile feedback on interactive elements. + * @param backgroundUtilitySelected Indicates an active or selected state. Used for selected + * messages, active list items, and toggled controls. + * @param backgroundUtilityDisabled Background for non-interactive elements. Flattens the + * element visually to signal unavailability. + * @param backgroundUtilitySkeletonLoadingBase Base color for the default skeleton loading + * gradient. Used as the background tone for placeholder surfaces. + * @param backgroundUtilitySkeletonLoadingHighlight Highlight color for the default skeleton + * loading gradient. Used for the moving shimmer to indicate loading activity. + * @param borderUtilityHover Applied on pointer hover over bordered interactive elements. + * @param borderUtilityPressed Applied during an active press on bordered interactive elements. + * @param borderUtilitySelected Border for selected or active items. Used for highlighted list + * items and active controls. + * @param borderUtilityFocused Focus ring for keyboard and accessibility navigation. Uses the + * brand color to signal input focus. + * @param borderUtilityActive Active input border. Used for focused text fields and selected + * form elements. + * @param borderUtilitySuccess Border for positive or confirmed states. Used on validated inputs + * and success indicators. + * @param borderUtilityWarning Border for cautionary states. Used on inputs or elements + * requiring attention. + * @param borderUtilityError Border for failure or invalid states. Used on failed inputs and + * destructive confirmations. + * @param borderUtilityDisabled Border for non-interactive elements. Matches the disabled + * surface to visually flatten the element. + * @param borderUtilityDisabledOnSurface Border for disabled elements on elevated surfaces. + * Stays visually distinct from the surface without drawing attention to a non-interactive + * element. + * @param borderCoreDefault Standard border for surfaces and containers. Used for input fields, + * cards, and dividers on neutral backgrounds. + * @param borderCoreSubtle A lighter border for minimal separation. Used where a full-strength + * border would feel too heavy. + * @param borderCoreStrong An emphatic border for elements that need clear definition. Used for + * focused containers and prominent dividers. + * @param borderCoreOnAccent Border on accent-colored surfaces. Stays white in both modes since + * the accent background doesn't invert. + * @param borderCoreOnSurface Border for elements sitting on elevated surfaces. Uses a stronger + * value than default to maintain visible separation when the background is darker. + * @param borderCoreOnElevation Border for controls placed on an elevated surface. Stronger in + * dark mode, where the lifted surface would otherwise close the contrast gap. + * @param borderCoreOpacitySubtle A very light transparent border. Used as a frame treatment on + * images and media attachments. + * @param borderCoreOpacityStrong A stronger transparent border for elements on colored or dark + * backgrounds. Used for waveform bars and similar treatments. + * @param borderCoreOnInverse Border on inverse-colored surfaces. Stays legible when the + * background flips between light and dark mode. + * @param avatarPresenceBorder The thin outline around the presence dot. Matches the local + * surface behind the avatar. In high-contrast it uses the base surface. + * @param avatarBgDefault avatar bg default + * @param avatarBgPlaceholder avatar bg placeholder + * @param avatarTextDefault avatar text default + * @param avatarTextPlaceholder avatar text placeholder + * @param systemBgBlur system bg-blur + * @param systemCaret system caret + * @param systemScrollbar system scrollbar + */ + @StreamPublishedApi + @Immutable + public data class Colors( + public val brand: ColorScale, + public val chrome: ChromeScale, + public val accentPrimary: Color, + public val accentSuccess: Color, + public val accentWarning: Color, + public val accentError: Color, + public val accentNeutral: Color, + public val textPrimary: Color, + public val textSecondary: Color, + public val textTertiary: Color, + public val textOnInverse: Color, + public val textOnAccent: Color, + public val textDisabled: Color, + public val textLink: Color, + public val backgroundCoreApp: Color, + public val backgroundCoreElevation0: Color, + public val backgroundCoreElevation1: Color, + public val backgroundCoreElevation2: Color, + public val backgroundCoreElevation3: Color, + public val backgroundCoreOnElevation: Color, + public val backgroundCoreSurfaceDefault: Color, + public val backgroundCoreSurfaceSubtle: Color, + public val backgroundCoreSurfaceCard: Color, + public val backgroundCoreSurfaceStrong: Color, + public val backgroundCoreInverse: Color, + public val backgroundCoreOnAccent: Color, + public val backgroundCoreHighlight: Color, + public val backgroundCoreOverlayLight: Color, + public val backgroundCoreOverlayDark: Color, + public val backgroundCoreScrim: Color, + public val backgroundCoreOverlayDarkStrong: Color, + public val backgroundUtilityHover: Color, + public val backgroundUtilityPressed: Color, + public val backgroundUtilitySelected: Color, + public val backgroundUtilityDisabled: Color, + public val backgroundUtilitySkeletonLoadingBase: Color, + public val backgroundUtilitySkeletonLoadingHighlight: Color, + public val borderUtilityHover: Color, + public val borderUtilityPressed: Color, + public val borderUtilitySelected: Color, + public val borderUtilityFocused: Color, + public val borderUtilityActive: Color, + public val borderUtilitySuccess: Color, + public val borderUtilityWarning: Color, + public val borderUtilityError: Color, + public val borderUtilityDisabled: Color, + public val borderUtilityDisabledOnSurface: Color, + public val borderCoreDefault: Color, + public val borderCoreSubtle: Color, + public val borderCoreStrong: Color, + public val borderCoreOnAccent: Color, + public val borderCoreOnSurface: Color, + public val borderCoreOnElevation: Color, + public val borderCoreOpacitySubtle: Color, + public val borderCoreOpacityStrong: Color, + public val borderCoreOnInverse: Color, + public val avatarPresenceBorder: Color, + public val avatarBgDefault: Color, + public val avatarBgPlaceholder: Color, + public val avatarTextDefault: Color, + public val avatarTextPlaceholder: Color, + public val systemBgBlur: Color, + public val systemCaret: Color, + public val systemScrollbar: Color, + ) { + + /** Main text inside an input field. */ + @StreamInternalApi public val inputTextDefault: Color = textPrimary + + /** Placeholder text for the input. Lower emphasis than main text. */ + @StreamInternalApi public val inputTextPlaceholder: Color = textTertiary + + /** Text inside the input when it is disabled. */ + @StreamInternalApi public val inputTextDisabled: Color = textDisabled + + /** + * Icons inside the input area (attach, emoji, camera, send when idle). Matches tertiary + * text strength. + */ + @StreamInternalApi public val inputTextIcon: Color = textTertiary + + /** + * Icons inside the input area in their active state. Steps up from the resting icon colour + * to primary text. + */ + @StreamInternalApi public val inputTextIconActive: Color = textPrimary + + /** Background of the primary button. */ + @StreamInternalApi public val buttonPrimaryBg: Color = accentPrimary + + /** Background of the primary button in its liquid glass variant. */ + @StreamInternalApi public val buttonPrimaryBgLiquidGlass: Color = Color.Transparent + + /** Text of the primary button when it sits on a neutral background. */ + @StreamInternalApi public val buttonPrimaryText: Color = accentPrimary + + /** Text of the primary button when it sits on an accent background. */ + @StreamInternalApi public val buttonPrimaryTextOnAccent: Color = textOnAccent + + /** Text of the primary button when it sits on a dark background. */ + @StreamInternalApi public val buttonPrimaryTextOnDark: Color = textOnInverse + + /** Border of the primary button. */ + @StreamInternalApi public val buttonPrimaryBorder: Color = brand.s200 + + /** Border of the primary button when it sits on a dark background. */ + @StreamInternalApi public val buttonPrimaryBorderOnDark: Color = borderCoreOnInverse + + /** Background of the secondary button. */ + @StreamInternalApi public val buttonSecondaryBg: Color = backgroundCoreSurfaceDefault + + /** Background of the secondary button in its liquid glass variant. */ + @StreamInternalApi public val buttonSecondaryBgLiquidGlass: Color = backgroundCoreElevation0 + + /** Text of the secondary button. */ + @StreamInternalApi public val buttonSecondaryText: Color = textPrimary + + /** Text of the secondary button when it sits on an accent background. */ + @StreamInternalApi public val buttonSecondaryTextOnAccent: Color = textPrimary + + /** Text of the secondary button when it sits on a dark background. */ + @StreamInternalApi public val buttonSecondaryTextOnDark: Color = textOnInverse + + /** Border of the secondary button. */ + @StreamInternalApi public val buttonSecondaryBorder: Color = borderCoreDefault + + /** Border of the secondary button when it sits on a dark background. */ + @StreamInternalApi public val buttonSecondaryBorderOnDark: Color = borderCoreOnInverse + + /** Background of the destructive button. */ + @StreamInternalApi public val buttonDestructiveBg: Color = accentError + + /** Background of the destructive button in its liquid glass variant. */ + @StreamInternalApi + public val buttonDestructiveBgLiquidGlass: Color = backgroundCoreElevation0 + + /** Text of the destructive button when it sits on a neutral background. */ + @StreamInternalApi public val buttonDestructiveText: Color = accentError + + /** Text of the destructive button when it sits on an accent background. */ + @StreamInternalApi public val buttonDestructiveTextOnAccent: Color = textOnAccent + + /** Text of the destructive button when it sits on a dark background. */ + @StreamInternalApi public val buttonDestructiveTextOnDark: Color = textOnInverse + + /** Border of the destructive button. */ + @StreamInternalApi public val buttonDestructiveBorder: Color = accentError + + /** Border of the destructive button when it sits on a dark background. */ + @StreamInternalApi public val buttonDestructiveBorderOnDark: Color = textOnInverse + + /** + * The green online indicator. Uses success accent in normal themes. In high-contrast, color + * is dropped and replaced with strong black for maximum clarity. + */ + @StreamInternalApi public val avatarPresenceBgOnline: Color = accentSuccess + + /** The gray offline indicator. Uses the neutral accent. */ + @StreamInternalApi public val avatarPresenceBgOffline: Color = accentNeutral + + /** Text of system-level UI elements. */ + @StreamInternalApi public val systemText: Color = chrome.s1000 + + /** Background of the default badge. */ + @StreamInternalApi public val badgeBgDefault: Color = backgroundCoreElevation3 + + /** Background of the primary badge. */ + @StreamInternalApi public val badgeBgPrimary: Color = accentPrimary + + /** Background of the neutral badge. */ + @StreamInternalApi public val badgeBgNeutral: Color = accentNeutral + + /** Background of the error badge. */ + @StreamInternalApi public val badgeBgError: Color = accentError + + /** Background of the inverse badge. */ + @StreamInternalApi public val badgeBgInverse: Color = chrome.s1000 + + /** Background of the overlay badge. */ + @StreamInternalApi public val badgeBgOverlay: Color = Color(0xBF000000) + + /** Text of the default badge. */ + @StreamInternalApi public val badgeText: Color = textPrimary + + /** Text of the inverse badge. */ + @StreamInternalApi public val badgeTextOnInverse: Color = textOnInverse + + /** Text of a badge on an accent background. */ + @StreamInternalApi public val badgeTextOnAccent: Color = textOnAccent + + /** Border of the badge. */ + @StreamInternalApi public val badgeBorder: Color = borderCoreOnInverse + + /** Fill of the progress bar. */ + @StreamInternalApi public val controlProgressBarFill: Color = accentNeutral + + /** Track of the progress bar. */ + @StreamInternalApi public val controlProgressBarTrack: Color = backgroundCoreSurfaceStrong + + /** Progress fill for audio playback, distinct from the neutral fill used elsewhere. */ + @StreamInternalApi public val controlProgressBarFillAudio: Color = accentPrimary + + /** Background of the toggle switch when it is off. */ + @StreamInternalApi public val controlToggleSwitchBg: Color = accentNeutral + + /** Background of the toggle switch when it is on. */ + @StreamInternalApi public val controlToggleSwitchBgSelected: Color = accentPrimary + + /** Background of the toggle switch when it is disabled. */ + @StreamInternalApi + public val controlToggleSwitchBgDisabled: Color = backgroundUtilityDisabled + + /** Knob of the toggle switch. */ + @StreamInternalApi public val controlToggleSwitchKnob: Color = backgroundCoreOnAccent + + /** Background of the checkbox when it is unchecked. */ + @StreamInternalApi public val controlCheckboxBg: Color = Color.Transparent + + /** Border of the checkbox. */ + @StreamInternalApi public val controlCheckboxBorder: Color = borderCoreDefault + + /** Background of the checkbox when it is checked. */ + @StreamInternalApi public val controlCheckboxBgSelected: Color = accentPrimary + + /** Check mark of the checkbox. */ + @StreamInternalApi public val controlCheckboxIcon: Color = textOnAccent + + /** Background of the radio button when it is unselected. */ + @StreamInternalApi public val controlRadioButtonBg: Color = Color.Transparent + + /** Border of the radio button. */ + @StreamInternalApi public val controlRadioButtonBorder: Color = borderCoreDefault + + /** Background of the radio button when it is selected. */ + @StreamInternalApi public val controlRadioButtonBgSelected: Color = accentPrimary + + /** Indicator dot of the selected radio button. */ + @StreamInternalApi public val controlRadioButtonIndicator: Color = textOnAccent + + /** Background of the radio check when it is unselected. */ + @StreamInternalApi public val controlRadioCheckBg: Color = Color.Transparent + + /** Border of the radio check. */ + @StreamInternalApi public val controlRadioCheckBorder: Color = borderCoreDefault + + /** Background of the radio check when it is selected. */ + @StreamInternalApi public val controlRadioCheckBgSelected: Color = accentPrimary + + /** Check mark of the selected radio check. */ + @StreamInternalApi public val controlRadioCheckIcon: Color = textOnAccent + + /** Border of the chip. */ + @StreamInternalApi public val controlChipBorder: Color = borderCoreDefault + + /** Text of the chip. */ + @StreamInternalApi public val controlChipText: Color = textPrimary + + /** Background for a neutral label. */ + @StreamInternalApi public val labelBgNeutral: Color = chrome.s150 + + /** Background for a primary label. */ + @StreamInternalApi public val labelBgPrimary: Color = brand.s150 + + /** Text on a neutral label. */ + @StreamInternalApi public val labelTextNeutral: Color = textPrimary + + /** Text on a primary label. */ + @StreamInternalApi public val labelTextPrimary: Color = brand.s900 + + /** The underline on the selected tab. */ + @StreamInternalApi public val tabIndicator: Color = accentPrimary + + /** Label colour of an unselected tab. */ + @StreamInternalApi public val tabText: Color = textSecondary + + /** Label colour of the selected tab. */ + @StreamInternalApi public val tabTextSelected: Color = accentPrimary + + /** The line running under the tab bar. */ + @StreamInternalApi public val tabTrack: Color = borderCoreDefault + + /** Factories for the default light and dark color sets. */ + public companion object { + /** + * The default colors for the light theme. + * + * @param brand The brand color scale. Defaults to [ColorScale.defaultLight]. + * @param chrome The chrome color scale. Defaults to [ChromeScale.defaultLight]. + */ + public fun default( + brand: ColorScale = ColorScale.defaultLight(), + chrome: ChromeScale = ChromeScale.defaultLight(), + ): Colors = + Colors( + brand = brand, + chrome = chrome, + accentPrimary = brand.s500, + accentSuccess = StreamPrimitiveColors.green400, + accentWarning = StreamPrimitiveColors.yellow200, + accentError = StreamPrimitiveColors.red500, + accentNeutral = chrome.s500, + textPrimary = chrome.s900, + textSecondary = chrome.s700, + textTertiary = chrome.s500, + textOnInverse = chrome.s0, + textOnAccent = chrome.s0, + textDisabled = chrome.s300, + textLink = brand.s500, + backgroundCoreApp = chrome.s0, + backgroundCoreElevation0 = chrome.s0, + backgroundCoreElevation1 = chrome.s0, + backgroundCoreElevation2 = chrome.s0, + backgroundCoreElevation3 = chrome.s0, + backgroundCoreOnElevation = chrome.s100, + backgroundCoreSurfaceDefault = chrome.s100, + backgroundCoreSurfaceSubtle = chrome.s50, + backgroundCoreSurfaceCard = chrome.s50, + backgroundCoreSurfaceStrong = chrome.s150, + backgroundCoreInverse = chrome.s1000, + backgroundCoreOnAccent = chrome.s0, + backgroundCoreHighlight = StreamPrimitiveColors.yellow50, + backgroundCoreOverlayLight = Color(0xBFFFFFFF), + backgroundCoreOverlayDark = Color(0x401A1B25), + backgroundCoreScrim = Color(0x801A1B25), + backgroundCoreOverlayDarkStrong = Color(0xBF1A1B25), + backgroundUtilityHover = Color(0x1A1A1B25), + backgroundUtilityPressed = Color(0x261A1B25), + backgroundUtilitySelected = Color(0x331A1B25), + backgroundUtilityDisabled = chrome.s100, + backgroundUtilitySkeletonLoadingBase = Color.Transparent, + backgroundUtilitySkeletonLoadingHighlight = Color(0xBFFFFFFF), + borderUtilityHover = Color(0x1A1A1B25), + borderUtilityPressed = Color(0x331A1B25), + borderUtilitySelected = Color(0x261A1B25), + borderUtilityFocused = brand.s150, + borderUtilityActive = brand.s500, + borderUtilitySuccess = StreamPrimitiveColors.green400, + borderUtilityWarning = StreamPrimitiveColors.yellow200, + borderUtilityError = StreamPrimitiveColors.red500, + borderUtilityDisabled = chrome.s100, + borderUtilityDisabledOnSurface = chrome.s150, + borderCoreDefault = chrome.s150, + borderCoreSubtle = chrome.s100, + borderCoreStrong = chrome.s300, + borderCoreOnAccent = chrome.s0, + borderCoreOnSurface = chrome.s300, + borderCoreOnElevation = chrome.s150, + borderCoreOpacitySubtle = Color(0x1A1A1B25), + borderCoreOpacityStrong = Color(0x401A1B25), + borderCoreOnInverse = chrome.s0, + avatarPresenceBorder = chrome.s0, + avatarBgDefault = brand.s150, + avatarBgPlaceholder = chrome.s150, + avatarTextDefault = brand.s900, + avatarTextPlaceholder = chrome.s500, + systemBgBlur = Color(0x03FFFFFF), + systemCaret = brand.s500, + systemScrollbar = Color(0x80000000), + ) + + /** + * The default colors for the dark theme. + * + * @param brand The brand color scale. Defaults to [ColorScale.defaultDark]. + * @param chrome The chrome color scale. Defaults to [ChromeScale.defaultDark]. + */ + public fun defaultDark( + brand: ColorScale = ColorScale.defaultDark(), + chrome: ChromeScale = ChromeScale.defaultDark(), + ): Colors = + Colors( + brand = brand, + chrome = chrome, + accentPrimary = brand.s400, + accentSuccess = StreamPrimitiveColors.green300, + accentWarning = StreamPrimitiveColors.yellow150, + accentError = StreamPrimitiveColors.red400, + accentNeutral = chrome.s500, + textPrimary = chrome.s900, + textSecondary = chrome.s700, + textTertiary = chrome.s500, + textOnInverse = chrome.s0, + textOnAccent = chrome.s1000, + textDisabled = chrome.s300, + textLink = brand.s600, + backgroundCoreApp = chrome.s0, + backgroundCoreElevation0 = chrome.s0, + backgroundCoreElevation1 = chrome.s50, + backgroundCoreElevation2 = chrome.s100, + backgroundCoreElevation3 = chrome.s200, + backgroundCoreOnElevation = chrome.s150, + backgroundCoreSurfaceDefault = chrome.s100, + backgroundCoreSurfaceSubtle = chrome.s50, + backgroundCoreSurfaceCard = chrome.s100, + backgroundCoreSurfaceStrong = chrome.s150, + backgroundCoreInverse = chrome.s1000, + backgroundCoreOnAccent = chrome.s1000, + backgroundCoreHighlight = StreamPrimitiveColors.yellow800, + backgroundCoreOverlayLight = Color(0xBF000000), + backgroundCoreOverlayDark = Color(0x80000000), + backgroundCoreScrim = Color(0xBF000000), + backgroundCoreOverlayDarkStrong = Color(0xBF000000), + backgroundUtilityHover = Color(0x26FFFFFF), + backgroundUtilityPressed = Color(0x33FFFFFF), + backgroundUtilitySelected = Color(0x40FFFFFF), + backgroundUtilityDisabled = chrome.s100, + backgroundUtilitySkeletonLoadingBase = Color.Transparent, + backgroundUtilitySkeletonLoadingHighlight = Color(0xBF000000), + borderUtilityHover = Color(0x1AFFFFFF), + borderUtilityPressed = Color(0x33FFFFFF), + borderUtilitySelected = Color(0x26FFFFFF), + borderUtilityFocused = brand.s150, + borderUtilityActive = brand.s400, + borderUtilitySuccess = StreamPrimitiveColors.green300, + borderUtilityWarning = StreamPrimitiveColors.yellow150, + borderUtilityError = StreamPrimitiveColors.red400, + borderUtilityDisabled = chrome.s100, + borderUtilityDisabledOnSurface = chrome.s150, + borderCoreDefault = chrome.s200, + borderCoreSubtle = chrome.s100, + borderCoreStrong = chrome.s300, + borderCoreOnAccent = chrome.s1000, + borderCoreOnSurface = chrome.s300, + borderCoreOnElevation = chrome.s300, + borderCoreOpacitySubtle = Color(0x33FFFFFF), + borderCoreOpacityStrong = Color(0x40FFFFFF), + borderCoreOnInverse = chrome.s0, + avatarPresenceBorder = chrome.s0, + avatarBgDefault = brand.s150, + avatarBgPlaceholder = chrome.s150, + avatarTextDefault = brand.s900, + avatarTextPlaceholder = chrome.s500, + systemBgBlur = Color(0x03000000), + systemCaret = brand.s400, + systemScrollbar = Color(0x80FFFFFF), + ) + } + } + + /** + * An 11-stop color ramp for the brand (accent) palette. + * + * Light themes use the natural order (s50 lightest, s900 darkest); dark themes invert the + * mapping so perceived intensity stays consistent on dark backgrounds. + * + * Re-brand the whole UI from one color: + * ``` + * val purple = StreamDesign.ColorScale.from(brandColor = Color(0xFF6200EE)) + * VideoTheme(colors = StreamDesign.Colors.default(brand = purple)) + * ``` + * + * @param s50 The lightest stop in the natural order. + * @param s100 The 100 stop. + * @param s150 The 150 stop. + * @param s200 The 200 stop. + * @param s300 The 300 stop. + * @param s400 The 400 stop. + * @param s500 The base stop. [from] places the brand color here. + * @param s600 The 600 stop. + * @param s700 The 700 stop. + * @param s800 The 800 stop. + * @param s900 The darkest stop in the natural order. + */ + @StreamPublishedApi + @Immutable + public data class ColorScale( + public val s50: Color, + public val s100: Color, + public val s150: Color, + public val s200: Color, + public val s300: Color, + public val s400: Color, + public val s500: Color, + public val s600: Color, + public val s700: Color, + public val s800: Color, + public val s900: Color, + ) { + + /** + * Returns a new scale with the stops mirrored around the center (s50 with s900, s100 with + * s800, and so on). Use it to derive a dark theme counterpart from a light brand ramp. + */ + @Suppress("DataClassContainsFunctions") + public fun inverted(): ColorScale = + ColorScale( + s50 = s900, + s100 = s800, + s150 = s700, + s200 = s600, + s300 = s500, + s400 = s400, + s500 = s300, + s600 = s200, + s700 = s150, + s800 = s100, + s900 = s50, + ) + + /** Factories for the default brand scales and for a scale derived from one color. */ + public companion object { + /** The default brand scale for light themes. */ + public fun defaultLight(): ColorScale = + ColorScale( + s50 = StreamPrimitiveColors.blue50, + s100 = StreamPrimitiveColors.blue100, + s150 = StreamPrimitiveColors.blue150, + s200 = StreamPrimitiveColors.blue200, + s300 = StreamPrimitiveColors.blue300, + s400 = StreamPrimitiveColors.blue400, + s500 = StreamPrimitiveColors.blue500, + s600 = StreamPrimitiveColors.blue600, + s700 = StreamPrimitiveColors.blue700, + s800 = StreamPrimitiveColors.blue800, + s900 = StreamPrimitiveColors.blue900, + ) + + /** The default brand scale for dark themes. */ + public fun defaultDark(): ColorScale = + ColorScale( + s50 = StreamPrimitiveColors.blue900, + s100 = StreamPrimitiveColors.blue800, + s150 = StreamPrimitiveColors.blue700, + s200 = StreamPrimitiveColors.blue600, + s300 = StreamPrimitiveColors.blue500, + s400 = StreamPrimitiveColors.blue400, + s500 = StreamPrimitiveColors.blue300, + s600 = StreamPrimitiveColors.blue200, + s700 = StreamPrimitiveColors.blue150, + s800 = StreamPrimitiveColors.blue100, + s900 = StreamPrimitiveColors.blue50, + ) + + /** + * Generates a brand scale from a single [brandColor], placed at [s500]. Lighter stops + * are interpolated toward white and darker stops toward black. Use [inverted] for the + * dark theme counterpart. For exact branding provide an explicit [ColorScale]. + */ + public fun from(brandColor: Color): ColorScale = + ColorScale( + s50 = lerp(Color.White, brandColor, 0.04f), + s100 = lerp(Color.White, brandColor, 0.08f), + s150 = lerp(Color.White, brandColor, 0.16f), + s200 = lerp(Color.White, brandColor, 0.26f), + s300 = lerp(Color.White, brandColor, 0.42f), + s400 = lerp(Color.White, brandColor, 0.65f), + s500 = brandColor, + s600 = lerp(brandColor, Color.Black, 0.25f), + s700 = lerp(brandColor, Color.Black, 0.42f), + s800 = lerp(brandColor, Color.Black, 0.58f), + s900 = lerp(brandColor, Color.Black, 0.75f), + ) + } + } + + /** + * A 13-stop neutral ramp for the chrome (structural gray) palette: text, backgrounds, borders, + * surfaces. + * + * [s0] and [s1000] are the absolute endpoints (white and black in light, black and white in + * dark). They absorb the light and dark polarity, so downstream tokens reference chrome stops + * with the same expression in both themes. + * + * @param s0 The first endpoint: white in light themes, black in dark themes. + * @param s50 The 50 stop. + * @param s100 The 100 stop. + * @param s150 The 150 stop. + * @param s200 The 200 stop. + * @param s300 The 300 stop. + * @param s400 The 400 stop. + * @param s500 The 500 stop. + * @param s600 The 600 stop. + * @param s700 The 700 stop. + * @param s800 The 800 stop. + * @param s900 The 900 stop. + * @param s1000 The last endpoint: black in light themes, white in dark themes. + */ + @StreamPublishedApi + @Immutable + public data class ChromeScale( + public val s0: Color, + public val s50: Color, + public val s100: Color, + public val s150: Color, + public val s200: Color, + public val s300: Color, + public val s400: Color, + public val s500: Color, + public val s600: Color, + public val s700: Color, + public val s800: Color, + public val s900: Color, + public val s1000: Color, + ) { + /** Factories for the default chrome scales. */ + public companion object { + /** The default chrome scale for light themes. */ + public fun defaultLight(): ChromeScale = + ChromeScale( + s0 = StreamPrimitiveColors.baseWhite, + s50 = StreamPrimitiveColors.slate50, + s100 = StreamPrimitiveColors.slate100, + s150 = StreamPrimitiveColors.slate150, + s200 = StreamPrimitiveColors.slate200, + s300 = StreamPrimitiveColors.slate300, + s400 = StreamPrimitiveColors.slate400, + s500 = StreamPrimitiveColors.slate500, + s600 = StreamPrimitiveColors.slate600, + s700 = StreamPrimitiveColors.slate700, + s800 = StreamPrimitiveColors.slate800, + s900 = StreamPrimitiveColors.slate900, + s1000 = StreamPrimitiveColors.baseBlack, + ) + + /** The default chrome scale for dark themes. */ + public fun defaultDark(): ChromeScale = + ChromeScale( + s0 = StreamPrimitiveColors.baseBlack, + s50 = StreamPrimitiveColors.neutral900, + s100 = StreamPrimitiveColors.neutral800, + s150 = StreamPrimitiveColors.neutral700, + s200 = StreamPrimitiveColors.neutral600, + s300 = StreamPrimitiveColors.neutral500, + s400 = StreamPrimitiveColors.neutral400, + s500 = StreamPrimitiveColors.neutral300, + s600 = StreamPrimitiveColors.neutral200, + s700 = StreamPrimitiveColors.neutral150, + s800 = StreamPrimitiveColors.neutral100, + s900 = StreamPrimitiveColors.neutral50, + s1000 = StreamPrimitiveColors.baseWhite, + ) + } + } + + /** + * The text styles of the design system. + * + * @param bodyDefault Body text. + * @param bodyEmphasis Emphasized body text. + * @param bodyLink Body text that acts as a link. + * @param bodyLinkEmphasis Emphasized body text that acts as a link. + * @param captionDefault Captions and supplementary information. + * @param captionEmphasis Emphasized captions. + * @param captionLink Caption text that acts as a link. + * @param captionLinkEmphasis Emphasized caption text that acts as a link. + * @param headingExtraSmall Extra small headings and section labels. + * @param headingSmall Small headings. + * @param headingMedium Medium headings. + * @param headingLarge Large, prominent headings. + * @param metadataDefault Metadata and secondary information. + * @param metadataEmphasis Emphasized metadata. + * @param metadataLink Metadata text that acts as a link. + * @param metadataLinkEmphasis Emphasized metadata text that acts as a link. + * @param numericSmall Micro numeric indicators. + * @param numericMedium Medium numeric indicators, like an unread count. + * @param numericLarge Large numeric indicators. + * @param numericExtraLarge Extra large numeric indicators. + */ + @StreamPublishedApi + @Immutable + public data class Typography( + public val bodyDefault: TextStyle, + public val bodyEmphasis: TextStyle, + public val bodyLink: TextStyle, + public val bodyLinkEmphasis: TextStyle, + public val captionDefault: TextStyle, + public val captionEmphasis: TextStyle, + public val captionLink: TextStyle, + public val captionLinkEmphasis: TextStyle, + public val headingExtraSmall: TextStyle, + public val headingSmall: TextStyle, + public val headingMedium: TextStyle, + public val headingLarge: TextStyle, + public val metadataDefault: TextStyle, + public val metadataEmphasis: TextStyle, + public val metadataLink: TextStyle, + public val metadataLinkEmphasis: TextStyle, + public val numericSmall: TextStyle, + public val numericMedium: TextStyle, + public val numericLarge: TextStyle, + public val numericExtraLarge: TextStyle, + ) { + /** Factory for the default typography. */ + public companion object { + /** The default typography, optionally with a custom [fontFamily]. */ + public fun default(fontFamily: FontFamily? = null): Typography = + Typography( + bodyDefault = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightRegular, + fontSize = StreamTokens.fontSizeMd, + lineHeight = StreamTokens.lineHeightNormal, + ), + bodyEmphasis = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeMd, + lineHeight = StreamTokens.lineHeightNormal, + ), + bodyLink = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightRegular, + fontSize = StreamTokens.fontSizeMd, + lineHeight = StreamTokens.lineHeightNormal, + ), + bodyLinkEmphasis = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeMd, + lineHeight = StreamTokens.lineHeightNormal, + ), + captionDefault = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightRegular, + fontSize = StreamTokens.fontSizeSm, + lineHeight = StreamTokens.lineHeightNormal, + ), + captionEmphasis = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeSm, + lineHeight = StreamTokens.lineHeightNormal, + ), + captionLink = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightRegular, + fontSize = StreamTokens.fontSizeSm, + lineHeight = StreamTokens.lineHeightNormal, + ), + captionLinkEmphasis = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeSm, + lineHeight = StreamTokens.lineHeightNormal, + ), + headingExtraSmall = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeSm, + lineHeight = StreamTokens.lineHeightNormal, + ), + headingSmall = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeMd, + lineHeight = StreamTokens.lineHeightNormal, + ), + headingMedium = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeLg, + lineHeight = StreamTokens.lineHeightRelaxed, + ), + headingLarge = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeXl, + lineHeight = StreamTokens.lineHeightRelaxed, + ), + metadataDefault = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightRegular, + fontSize = StreamTokens.fontSizeXs, + lineHeight = StreamTokens.lineHeightTight, + ), + metadataEmphasis = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeXs, + lineHeight = StreamTokens.lineHeightTight, + ), + metadataLink = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightRegular, + fontSize = StreamTokens.fontSizeXs, + lineHeight = StreamTokens.lineHeightTight, + ), + metadataLinkEmphasis = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightSemiBold, + fontSize = StreamTokens.fontSizeXs, + lineHeight = StreamTokens.lineHeightTight, + ), + numericSmall = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightBold, + fontSize = StreamTokens.fontSizeMicro, + ), + numericMedium = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightBold, + fontSize = StreamTokens.fontSize2xs, + ), + numericLarge = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightBold, + fontSize = StreamTokens.fontSizeXs, + ), + numericExtraLarge = + TextStyle( + fontFamily = fontFamily, + fontWeight = StreamTokens.fontWeightBold, + fontSize = StreamTokens.fontSizeSm, + ), + ) + } + } +} diff --git a/stream-android-core-ui/src/main/java/io/getstream/android/core/ui/design/StreamPrimitiveColors.kt b/stream-android-core-ui/src/main/java/io/getstream/android/core/ui/design/StreamPrimitiveColors.kt new file mode 100644 index 0000000..dfa7d6e --- /dev/null +++ b/stream-android-core-ui/src/main/java/io/getstream/android/core/ui/design/StreamPrimitiveColors.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-core-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.android.core.ui.design + +import androidx.compose.ui.graphics.Color + +/** Raw color palette from the design system foundations. Semantic tokens reference these. */ +@Suppress("MagicNumber") +internal object StreamPrimitiveColors { + val baseBlack = Color(0xFF000000) + val baseWhite = Color(0xFFFFFFFF) + val blue50 = Color(0xFFF3F7FF) + val blue100 = Color(0xFFE3EDFF) + val blue150 = Color(0xFFC3D9FF) + val blue200 = Color(0xFFA5C5FF) + val blue300 = Color(0xFF78A8FF) + val blue400 = Color(0xFF4586FF) + val blue500 = Color(0xFF005FFF) + val blue600 = Color(0xFF1B53BD) + val blue700 = Color(0xFF19418D) + val blue800 = Color(0xFF142F63) + val blue900 = Color(0xFF091A3B) + val green300 = Color(0xFF00C384) + val green400 = Color(0xFF00A46E) + val neutral50 = Color(0xFFF8F8F8) + val neutral100 = Color(0xFFEFEFEF) + val neutral150 = Color(0xFFD8D8D8) + val neutral200 = Color(0xFFC4C4C4) + val neutral300 = Color(0xFFABABAB) + val neutral400 = Color(0xFF8F8F8F) + val neutral500 = Color(0xFF6A6A6A) + val neutral600 = Color(0xFF565656) + val neutral700 = Color(0xFF464646) + val neutral800 = Color(0xFF323232) + val neutral900 = Color(0xFF1C1C1C) + val red400 = Color(0xFFFC526A) + val red500 = Color(0xFFD90D10) + val slate50 = Color(0xFFF6F8FA) + val slate100 = Color(0xFFEBEEF1) + val slate150 = Color(0xFFD5DBE1) + val slate200 = Color(0xFFC0C8D2) + val slate300 = Color(0xFFA3ACBA) + val slate400 = Color(0xFF87909F) + val slate500 = Color(0xFF687385) + val slate600 = Color(0xFF545969) + val slate700 = Color(0xFF414552) + val slate800 = Color(0xFF30313D) + val slate900 = Color(0xFF1A1B25) + val yellow50 = Color(0xFFFEF9DA) + val yellow150 = Color(0xFFFCD579) + val yellow200 = Color(0xFFF6BF57) + val yellow300 = Color(0xFFFA922B) + val yellow400 = Color(0xFFF26D10) + val yellow800 = Color(0xFF5F1A05) +} diff --git a/stream-android-core-ui/src/main/java/io/getstream/android/core/ui/design/StreamTokens.kt b/stream-android-core-ui/src/main/java/io/getstream/android/core/ui/design/StreamTokens.kt new file mode 100644 index 0000000..9ca323f --- /dev/null +++ b/stream-android-core-ui/src/main/java/io/getstream/android/core/ui/design/StreamTokens.kt @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-core-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.android.core.ui.design + +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.getstream.android.core.annotations.StreamPublishedApi + +/** + * Layout, radius, typography and component primitives from the design system foundations. Semantic + * classes build on these; components use them directly. + */ +// Each token name states its role and its step in the scale, so the names are the documentation. +@Suppress("MagicNumber", "UndocumentedPublicProperty") +@StreamPublishedApi +public object StreamTokens { + + // Spacing + public val spacingNone: Dp = 0.dp + public val spacing3xs: Dp = 2.dp + public val spacing2xs: Dp = 4.dp + public val spacingXs: Dp = 8.dp + public val spacingSm: Dp = 12.dp + public val spacingMd: Dp = 16.dp + public val spacingLg: Dp = 20.dp + public val spacingXl: Dp = 24.dp + public val spacing2xl: Dp = 32.dp + public val spacing3xl: Dp = 40.dp + + // Radius + public val radiusNone: Dp = 0.dp + public val radius2xs: Dp = 2.dp + public val radiusXs: Dp = 4.dp + public val radiusSm: Dp = 6.dp + public val radiusMd: Dp = 8.dp + public val radiusLg: Dp = 12.dp + public val radiusXl: Dp = 16.dp + public val radius2xl: Dp = 20.dp + public val radius3xl: Dp = 24.dp + public val radius4xl: Dp = 32.dp + public val radiusMax: Dp = 9999.dp + + // Sizes + public val size2: Dp = 2.dp + public val size4: Dp = 4.dp + public val size6: Dp = 6.dp + public val size8: Dp = 8.dp + public val size10: Dp = 10.dp + public val size12: Dp = 12.dp + public val size13: Dp = 13.dp + public val size14: Dp = 14.dp + public val size15: Dp = 15.dp + public val size16: Dp = 16.dp + public val size17: Dp = 17.dp + public val size18: Dp = 18.dp + public val size20: Dp = 20.dp + public val size22: Dp = 22.dp + public val size24: Dp = 24.dp + public val size28: Dp = 28.dp + public val size32: Dp = 32.dp + public val size40: Dp = 40.dp + public val size48: Dp = 48.dp + public val size56: Dp = 56.dp + public val size64: Dp = 64.dp + public val size80: Dp = 80.dp + public val size128: Dp = 128.dp + public val size144: Dp = 144.dp + public val size208: Dp = 208.dp + public val size240: Dp = 240.dp + public val size320: Dp = 320.dp + public val size480: Dp = 480.dp + public val size560: Dp = 560.dp + public val size640: Dp = 640.dp + public val size760: Dp = 760.dp + + // Stroke + public val strokeW80: Dp = 0.8.dp + public val strokeW100: Dp = 1.dp + public val strokeW120: Dp = 1.2.dp + public val strokeW150: Dp = 1.5.dp + public val strokeW200: Dp = 2.dp + public val strokeW300: Dp = 3.dp + public val strokeW400: Dp = 4.dp + + // Typography + public val fontWeightRegular: FontWeight = FontWeight.W400 + public val fontWeightMedium: FontWeight = FontWeight.W500 + public val fontWeightSemiBold: FontWeight = FontWeight.W600 + public val fontWeightBold: FontWeight = FontWeight.W700 + public val fontSizeMicro: TextUnit = 8.sp + public val fontSize2xs: TextUnit = 10.sp + public val fontSizeXs: TextUnit = 12.sp + public val fontSizeSm: TextUnit = 14.sp + public val fontSizeMd: TextUnit = 16.sp + public val fontSizeLg: TextUnit = 18.sp + public val fontSizeXl: TextUnit = 20.sp + public val fontSize2xl: TextUnit = 24.sp + public val lineHeightTight: TextUnit = 16.sp + public val lineHeightNormal: TextUnit = 20.sp + public val lineHeightRelaxed: TextUnit = 24.sp + + // Components + public val deviceRadius: Dp = 32.dp + public val deviceSafeAreaBottom: Dp = 40.dp + public val deviceSafeAreaTop: Dp = 52.dp + public val buttonRadiusLg: Dp = 9999.dp + public val buttonRadiusMd: Dp = 9999.dp + public val buttonRadiusSm: Dp = 9999.dp + public val buttonRadiusFull: Dp = 9999.dp + public val buttonVisualHeightSm: Dp = 32.dp + public val buttonVisualHeightMd: Dp = 40.dp + public val buttonVisualHeightLg: Dp = 48.dp + public val buttonVisualHeightXs: Dp = 24.dp + public val buttonHitTargetMinHeight: Dp = 48.dp + public val buttonHitTargetMinWidth: Dp = 48.dp + public val buttonPaddingYLg: Dp = 14.dp + public val buttonPaddingYMd: Dp = 10.dp + public val buttonPaddingYSm: Dp = 6.dp + public val buttonPaddingYXs: Dp = 4.dp + public val buttonPaddingXIconOnlyLg: Dp = 14.dp + public val buttonPaddingXIconOnlyMd: Dp = 10.dp + public val buttonPaddingXIconOnlySm: Dp = 6.dp + public val buttonPaddingXIconOnlyXs: Dp = 4.dp + public val buttonPaddingXWithLabelLg: Dp = 16.dp + public val buttonPaddingXWithLabelMd: Dp = 16.dp + public val buttonPaddingXWithLabelSm: Dp = 16.dp + public val buttonPaddingXWithLabelXs: Dp = 12.dp + public val iconSizeXs: Dp = 12.dp + public val iconSizeSm: Dp = 16.dp + public val iconSizeMd: Dp = 20.dp + public val iconSizeLg: Dp = 32.dp + public val iconStrokeSubtle: Dp = 1.2.dp + public val iconStrokeDefault: Dp = 1.5.dp + public val iconStrokeEmphasis: Dp = 2.dp + public val emojiSm: TextUnit = 16.sp + public val emojiMd: TextUnit = 24.sp + public val emojiLg: TextUnit = 32.sp + public val emojiXl: TextUnit = 48.sp + public val emoji2xl: TextUnit = 64.sp + public val inputRadiusTextInput: Dp = 16.dp + public val inputRadiusSelectInput: Dp = 16.dp + public val inputRadiusSearchInput: Dp = 9999.dp + public val inputRadiusOptionCard: Dp = 24.dp +} diff --git a/stream-android-core-ui/src/test/java/io/getstream/android/core/ui/design/StreamDesignTest.kt b/stream-android-core-ui/src/test/java/io/getstream/android/core/ui/design/StreamDesignTest.kt new file mode 100644 index 0000000..b41d1cd --- /dev/null +++ b/stream-android-core-ui/src/test/java/io/getstream/android/core/ui/design/StreamDesignTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-core-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.android.core.ui.design + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.text.font.FontFamily +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +internal class StreamDesignTest { + + @Test + fun `inverting the light brand scale gives the dark brand scale`() { + assertEquals( + StreamDesign.ColorScale.defaultDark(), + StreamDesign.ColorScale.defaultLight().inverted(), + ) + } + + @Test + fun `inverting twice gives the original scale`() { + val scale = StreamDesign.ColorScale.defaultLight() + assertEquals(scale, scale.inverted().inverted()) + } + + @Test + fun `from places the brand color at s500 and orders the stops from light to dark`() { + val brand = Color(0xFF6200EE) + val scale = StreamDesign.ColorScale.from(brand) + assertEquals(brand, scale.s500) + val stops = + listOf( + scale.s50, + scale.s100, + scale.s150, + scale.s200, + scale.s300, + scale.s400, + scale.s500, + scale.s600, + scale.s700, + scale.s800, + scale.s900, + ) + stops.zipWithNext().forEach { (lighter, darker) -> + assertTrue( + lighter.luminance() > darker.luminance(), + "$lighter is not lighter than $darker", + ) + } + } + + @Test + fun `chrome endpoints flip between light and dark`() { + val light = StreamDesign.ChromeScale.defaultLight() + val dark = StreamDesign.ChromeScale.defaultDark() + assertEquals(Color.White, light.s0) + assertEquals(Color.Black, light.s1000) + assertEquals(Color.Black, dark.s0) + assertEquals(Color.White, dark.s1000) + } + + @Test + fun `default colors take the accent from the brand scale`() { + assertEquals( + StreamDesign.ColorScale.defaultLight().s500, + StreamDesign.Colors.default().accentPrimary, + ) + assertEquals( + StreamDesign.ColorScale.defaultDark().s400, + StreamDesign.Colors.defaultDark().accentPrimary, + ) + } + + @Test + fun `a custom brand scale flows into the derived component tokens`() { + val brand = StreamDesign.ColorScale.from(Color(0xFF6200EE)) + val colors = StreamDesign.Colors.default(brand = brand) + assertEquals(brand.s500, colors.buttonPrimaryBg) + assertEquals(brand.s200, colors.buttonPrimaryBorder) + assertEquals(brand.s150, colors.labelBgPrimary) + assertEquals(brand.s500, colors.textLink) + } + + @Test + fun `copying a semantic token does not change the derived tokens built from other tokens`() { + val colors = StreamDesign.Colors.default().copy(accentPrimary = Color.Red) + assertEquals(Color.Red, colors.buttonPrimaryBg) + assertEquals(StreamDesign.Colors.default().buttonSecondaryBg, colors.buttonSecondaryBg) + } + + @Test + fun `default typography applies the font family to every style`() { + val fontFamily = FontFamily.Monospace + val typography = StreamDesign.Typography.default(fontFamily = fontFamily) + val styles = + listOf( + typography.bodyDefault, + typography.bodyEmphasis, + typography.bodyLink, + typography.bodyLinkEmphasis, + typography.captionDefault, + typography.captionEmphasis, + typography.captionLink, + typography.captionLinkEmphasis, + typography.headingExtraSmall, + typography.headingSmall, + typography.headingMedium, + typography.headingLarge, + typography.metadataDefault, + typography.metadataEmphasis, + typography.metadataLink, + typography.metadataLinkEmphasis, + typography.numericSmall, + typography.numericMedium, + typography.numericLarge, + typography.numericExtraLarge, + ) + styles.forEach { assertEquals(fontFamily, it.fontFamily) } + } +}