identity: shared M3 Expressive theme factory + nav slide spec
FloretExpressiveTheme(lightScheme, darkScheme, darkTheme, dynamicColor, typography) holds the family's theme mechanics — dynamic-colour resolution (API 31+), system light/dark fallback, and the STANDARD motion scheme — while each app supplies its own seed-derived colour schemes, so the look stays per-app and the machinery is shared. rememberNavSlideSpec() shares the fast spatial nav slide. android-library, Compose-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
39
identity/build.gradle.kts
Normal file
39
identity/build.gradle.kts
Normal file
@@ -0,0 +1,39 @@
|
||||
// identity — the family's Material 3 Expressive theme factory + shared motion.
|
||||
// Holds the theme MECHANICS (dynamic-colour resolution, light/dark, the STANDARD
|
||||
// motion scheme) and the nav slide spec; each app supplies its own seed-derived
|
||||
// colour schemes, so the look stays per-app while the machinery is shared.
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "de.jeanlucmakiola.floret.identity"
|
||||
compileSdk = 37
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 29
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.ui)
|
||||
implementation(libs.androidx.foundation)
|
||||
implementation(libs.androidx.material3)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.jeanlucmakiola.floret.identity
|
||||
|
||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
|
||||
/**
|
||||
* The horizontal slide spec for screen-to-screen navigation: the *fast*
|
||||
* spring-physics spec from the active motion scheme — snappy with a subtle
|
||||
* springy settle, rather than a fixed easing curve. Shared so the family's apps
|
||||
* move the same way. Read in composable scope so the non-composable `NavHost`
|
||||
* transition lambdas can capture it.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun rememberNavSlideSpec(): FiniteAnimationSpec<IntOffset> =
|
||||
MaterialTheme.motionScheme.fastSpatialSpec()
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.jeanlucmakiola.floret.identity
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialExpressiveTheme
|
||||
import androidx.compose.material3.MotionScheme
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
/**
|
||||
* The family's Material 3 Expressive theme factory. Honours system light/dark
|
||||
* and Material You dynamic colour (API 31+), falling back to the app-supplied
|
||||
* [lightScheme] / [darkScheme] otherwise. Routes all component + custom motion
|
||||
* through `MaterialTheme.motionScheme` with the **standard** scheme — spring
|
||||
* choreography without the overshoot, the family's deliberate motion choice.
|
||||
*
|
||||
* The mechanics are shared; the look is not. Each app keeps its identity by
|
||||
* passing its own hand-tuned, seed-derived [lightScheme] / [darkScheme] and an
|
||||
* optional [typography]. A thin per-app wrapper (e.g. `AgendulaTheme`) supplies
|
||||
* those and forwards [darkTheme] / [dynamicColor] from settings.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun FloretExpressiveTheme(
|
||||
lightScheme: ColorScheme,
|
||||
darkScheme: ColorScheme,
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
dynamicColor: Boolean = true,
|
||||
typography: Typography = Typography(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val ctx = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(ctx) else dynamicLightColorScheme(ctx)
|
||||
}
|
||||
darkTheme -> darkScheme
|
||||
else -> lightScheme
|
||||
}
|
||||
|
||||
MaterialExpressiveTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = typography,
|
||||
motionScheme = MotionScheme.standard(),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user