Skip to content

Instantly share code, notes, and snippets.

@parthdesai1208
Last active March 17, 2022 06:06
Show Gist options
  • Select an option

  • Save parthdesai1208/b2de9289595ce7b2e9ad403b279bc1a0 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
Author

parthdesai1208 commented Mar 15, 2022

Image for light/dark theme

image
image

@parthdesai1208
Copy link
Copy Markdown
Author

parthdesai1208 commented Mar 15, 2022

Elevation for light/dark theme

image
image

@parthdesai1208
Copy link
Copy Markdown
Author

Dynamic theme

dynamic.theme.mp4

@parthdesai1208
Copy link
Copy Markdown
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