Skip to content

Instantly share code, notes, and snippets.

@parthdesai1208
Last active March 17, 2022 06:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save parthdesai1208/b2de9289595ce7b2e9ad403b279bc1a0 to your computer and use it in GitHub Desktop.
Save parthdesai1208/b2de9289595ce7b2e9ad403b279bc1a0 to your computer and use it in GitHub Desktop.
*********************************************************************************************************************************
Image
*********************************************************************************************************************************
Usage:
painter = painterResource(id = OwlTheme.images.lockupLogo)
---------------------------------------------------------------------------------
data class:
@Immutable
data class Images(@DrawableRes val lockupLogo: Int)
internal val LocalImages = staticCompositionLocalOf<Images> {
error("No LocalImages specified")
}
---------------------------------------------------------------------------------
theme.kt:
private val LightImages = Images(lockupLogo = R.drawable.ic_lockup_blue)
private val DarkImages = Images(lockupLogo = R.drawable.ic_lockup_white)
//inside your theme compose function
val images = if (darkTheme) DarkImages else LightImages
CompositionLocalProvider(
LocalImages provides images
) {
MaterialTheme() //here
}
//create object
object OwlTheme {
val images: Images
@Composable
get() = LocalImages.current
}
*********************************************************************************************************************************
Elevations
*********************************************************************************************************************************
Usage:
elevation = OwlTheme.elevations.card
---------------------------------------------------------------------------------
data class:
@Immutable
data class Elevations(val card: Dp = 0.dp)
internal val LocalElevations = staticCompositionLocalOf { Elevations() }
---------------------------------------------------------------------------------
theme.kt:
private val LightElevation = Elevations()
private val DarkElevation = Elevations(card = 1.dp)
//inside your theme compose function
val elevation = if (darkTheme) DarkElevation else LightElevation
CompositionLocalProvider(
LocalElevations provides elevation
) {
MaterialTheme() //here
}
//create object
object OwlTheme {
val elevations: Elevations
@Composable
get() = LocalElevations.current
}
*********************************************************************************************************************************
Dynamic theme
*********************************************************************************************************************************
//here we want to implement dynamic theme, based on image, relative color will apply to its background
//use it like
DynamicTheme("image_url_here"){
//other compose functions here
}
//implementation
@Composable
private fun DynamicTheme(
podcastImageUrl: String,
content: @Composable () -> Unit
) {
val surfaceColor = MaterialTheme.colors.surface
val dominantColorState = rememberDominantColorState(
defaultColor = MaterialTheme.colors.surface
) { color ->
// We want a color which has sufficient contrast against the surface color
color.contrastAgainst(surfaceColor) >= 3f
}
DynamicThemePrimaryColorsFromImage(dominantColorState) {
// Update the dominantColorState with colors coming from the podcast image URL
LaunchedEffect(podcastImageUrl) {
//LaunchedEffect = We need to launch CoroutineScope
if (podcastImageUrl.isNotEmpty()) {
dominantColorState.updateColorsFromImageUrl(podcastImageUrl)
} else {
dominantColorState.reset()
}
}
content()
}
}
---------------------------------------------------------------------------------
@Composable
fun DynamicThemePrimaryColorsFromImage(
dominantColorState: DominantColorState = rememberDominantColorState(),
content: @Composable () -> Unit
) {
//here we first copy whole material color instance & apply some tweaks on some colors
val colors = MaterialTheme.colors.copy(
primary = animateColorAsState(
dominantColorState.color,
spring(stiffness = Spring.StiffnessLow)
).value,
onPrimary = animateColorAsState(
dominantColorState.onColor,
spring(stiffness = Spring.StiffnessLow)
).value
)
MaterialTheme(colors = colors, content = content)
}
---------------------------------------------------------------------------------
@Composable
fun rememberDominantColorState(
context: Context = LocalContext.current,
defaultColor: Color = MaterialTheme.colors.primary,
defaultOnColor: Color = MaterialTheme.colors.onPrimary,
cacheSize: Int = 12,
isColorValid: (Color) -> Boolean = { true }
): DominantColorState = remember {
DominantColorState(context, defaultColor, defaultOnColor, cacheSize, isColorValid)
}
---------------------------------------------------------------------------------
@Stable
class DominantColorState(
private val context: Context,
private val defaultColor: Color,
private val defaultOnColor: Color,
cacheSize: Int = 12,
private val isColorValid: (Color) -> Boolean = { true }
) {
var color by mutableStateOf(defaultColor)
private set
var onColor by mutableStateOf(defaultOnColor)
private set
private val cache = when {
cacheSize > 0 -> LruCache<String, DominantColors>(cacheSize)
else -> null
}
suspend fun updateColorsFromImageUrl(url: String) {
val result = calculateDominantColor(url)
color = result?.color ?: defaultColor
onColor = result?.onColor ?: defaultOnColor
}
private suspend fun calculateDominantColor(url: String): DominantColors? {
val cached = cache?.get(url)
if (cached != null) {
// If we already have the result cached, return early now...
return cached
}
// Otherwise we calculate the swatches in the image, and return the first valid color
return calculateSwatchesInImage(context, url)
// First we want to sort the list by the color's population
.sortedByDescending { swatch -> swatch.population }
// Then we want to find the first valid color
.firstOrNull { swatch -> isColorValid(Color(swatch.rgb)) }
// If we found a valid swatch, wrap it in a [DominantColors]
?.let { swatch ->
DominantColors(
color = Color(swatch.rgb),
onColor = Color(swatch.bodyTextColor).copy(alpha = 1f)
)
}
// Cache the resulting [DominantColors]
?.also { result -> cache?.put(url, result) }
}
/**
* Reset the color values to [defaultColor].
*/
fun reset() {
color = defaultColor
onColor = defaultColor
}
}
*********************************************************************************************************************************
Custom design system - Basic syntax below
example - https://github.com/android/compose-samples/blob/main/Jetsnack/app/src/main/java/com/example/jetsnack/ui/theme/Theme.kt
*********************************************************************************************************************************
customDesignTheme {
//your content/compose here
}
customDesignTheme(darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit) {
val colors = if (darkTheme) DarkColorPalette else LightColorPalette
val sysUiController = rememberSystemUiController()
//implementation "com.google.accompanist:accompanist-systemuicontroller:XXX"
SideEffect {
sysUiController.setSystemBarsColor(
color = colors.uiBackground.copy(alpha = AlphaNearOpaque)
//note: we can define uiBackground same as brand in CustomDesignClass class
)
}
ProvideJetsnackColors(colors) {
MaterialTheme(
colors = defaultColors(darkTheme),
typography = Typography,
shapes = Shapes,
content = content
)
}
}
private val LightColorPalette = CustomDesignClass(brand = Shadow5) //val Shadow5 = Color(0xff4b30ed)
private val DarkColorPalette = CustomDesignClass(brand = Shadow1) //val Shadow1 = Color(0xffded6fe)
@Stable
class CustomDesignClass(brand: Color){
var brand by mutableStateOf(brand)
private set
fun update(other: CustomDesignClass) {
brand = other.brand
}
fun copy(): CustomDesignClass{ CustomDesignClass(brand = brand) }
}
@Composable
fun ProvideJetsnackColors(
colors: CustomDesignClass,
content: @Composable () -> Unit
) {
val colorPalette = remember {
// Explicitly creating a new object here so we don't mutate the initial [colors]
// provided, and overwrite the values set in it.
colors.copy()
}
colorPalette.update(colors)
CompositionLocalProvider(LocalJetsnackColors provides colorPalette, content = content)
}
private val LocalJetsnackColors = staticCompositionLocalOf<CustomDesignClass> {
error("No JetsnackColorPalette provided")
}
fun defaultColors(
darkTheme: Boolean,
debugColor: Color = Color.Magenta
) = Colors(
primary = debugColor,
primaryVariant = debugColor,
secondary = debugColor,
secondaryVariant = debugColor,
background = debugColor,
surface = debugColor,
error = debugColor,
onPrimary = debugColor,
onSecondary = debugColor,
onBackground = debugColor,
onSurface = debugColor,
onError = debugColor,
isLight = !darkTheme
)
object JetsnackTheme {
val colors: CustomDesignClass
@Composable
get() = LocalJetsnackColors.current
}
Use it like,
JetsnackTheme.colors.brand
JetsnackTheme.colors.brand.copy(alpha = 0.12f) [0..1]
@parthdesai1208
Copy link
Author

parthdesai1208 commented Mar 15, 2022

Image for light/dark theme

image
image

@parthdesai1208
Copy link
Author

parthdesai1208 commented Mar 15, 2022

Elevation for light/dark theme

image
image

@parthdesai1208
Copy link
Author

Dynamic theme

dynamic.theme.mp4

@parthdesai1208
Copy link
Author

Custom design system

light

custom.design.light.theme.mp4

dark

custom.design.dark.theme.mp4

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