Last active
March 17, 2022 06:06
-
-
Save parthdesai1208/b2de9289595ce7b2e9ad403b279bc1a0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
********************************************************************************************************************************* | |
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] |
Dynamic theme
dynamic.theme.mp4
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
Image for light/dark theme