Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ Add the following dependency to your `build.gradle` / `build.gradle.kts` file:
For Groovy - `build.gradle`:
````
dependencies {
implementation 'com.github.KvColorPalette:KvColorPalette-Android:3.1.0'
implementation 'com.github.KvColorPalette:KvColorPalette-Android:3.2.0'
}
````
For Kotlin DSL - `build.gradle.kts`:
````
dependencies {
implementation("com.github.KvColorPalette:KvColorPalette-Android:3.1.0")
implementation("com.github.KvColorPalette:KvColorPalette-Android:3.2.0")
}
````

Expand Down Expand Up @@ -102,8 +102,10 @@ In this `KvColorPalette.colorSchemeThemePalette` you will have following color a
|.tertiary |available |available |Suggesting tertiary color. |
|.quaternary |available |available |Suggesting quaternary color. |
|.background |available |available |Suggesting background color. |
|.surface |available |available |Suggesting background color. |
|.onPrimary |available |available |This is the color you can use on any component use primary color. |
|.onSecondary |available |available |This is the color you can use on any component use secondary color. |
|.onSurface |available |available |This is the color you can use on any component use secondary color. |
|.shadow |available |available |This is the color for your shadows. |

This `ColorSchemaThemePalette` is another Android Jetpack Compose `ColorSchema`. But that contains additional attributes like `base`, `quaternary`, `shadow` that provide
Expand Down
5 changes: 5 additions & 0 deletions gradle/version-catalog/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ material = "1.12.0"
composeMaterial = "1.7.6"
composeNavigation = "2.8.5"

googleTruthVersion = "1.4.4"
mockKVersion = "1.14.5"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
Expand All @@ -40,6 +43,8 @@ androidx-navigation-compose = { group = "androidx.navigation", name = "navigatio
material = { group = "com.google.android.material", name = "material", version.ref = "material" }

junit = { group = "junit", name = "junit", version.ref = "junit" }
google-truth = { group = "com.google.truth", name="truth", version.ref = "googleTruthVersion" }
mockk = { group = "io.mockk", name="mockk", version.ref = "mockKVersion" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
Expand Down
2 changes: 2 additions & 0 deletions kv-color-palette/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ dependencies {
implementation(libs.androidx.ui.graphics)

testImplementation(libs.junit)
testImplementation(libs.google.truth)
testImplementation(libs.mockk)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
Expand Down
2 changes: 1 addition & 1 deletion kv-color-palette/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
kvColorPaletteGroupId=com.github.KvColorPalette
kvColorPaletteArtifactId=KvColorPalette-Android
kvColorPaletteVersion=3.1.0
kvColorPaletteVersion=3.2.0
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
import com.kavi.droid.color.palette.extension.base
import com.kavi.droid.color.palette.extension.hsl
import com.kavi.droid.color.palette.extension.isHighLightColor
import com.kavi.droid.color.palette.model.ColorSchemeThemePalette
import com.kavi.droid.color.palette.model.ThemeGenMode

Expand Down Expand Up @@ -96,13 +97,17 @@ object ThemeGenUtil {
* @return A light theme color set. [ColorScheme]
*/
private fun generateThemeLightColorScheme(givenColor: Color): ColorScheme {
val secondaryColor = generateLightSecondaryColor(givenColor)
val backgroundColor = generateLightBackgroundColor(givenColor)
val lightColorScheme = lightColorScheme(
primary = givenColor,
secondary = generateLightSecondaryColor(givenColor),
secondary = secondaryColor,
tertiary = generateLightTertiaryColor(givenColor),
background = generateLightBackgroundColor(givenColor),
onPrimary = Color.White,
onSecondary = Color.White
background = backgroundColor,
surface = ColorUtil.blendColors(firstColor = backgroundColor, secondColor = Color.White, .9f),
onPrimary = generateOverTheTopLightColor(givenColor),
onSecondary = generateOverTheTopLightColor(secondaryColor),
onSurface = ColorUtil.blendColors(firstColor = Color.Black, Color.White, .25f)
)
lightColorScheme.base = givenColor

Expand All @@ -119,27 +124,33 @@ object ThemeGenUtil {
private fun generateMultiInputThemeLightColorScheme(givenColor: Color, secondColor: Color, blendColor: Color? = null, themeGenMode: ThemeGenMode = ThemeGenMode.SEQUENCE): ColorScheme {
when (themeGenMode) {
ThemeGenMode.SEQUENCE -> {
val backgroundColor = generateLightBackgroundColor(givenColor)
val light = lightColorScheme(
primary = givenColor,
secondary = secondColor,
tertiary = generateLightTertiaryColor(givenColor),
background = generateLightBackgroundColor(givenColor),
onPrimary = Color.White,
onSecondary = Color.White
background = backgroundColor,
surface = ColorUtil.blendColors(firstColor = backgroundColor, secondColor = Color.White, .9f),
onPrimary = generateOverTheTopLightColor(givenColor),
onSecondary = generateOverTheTopLightColor(secondColor),
onSurface = ColorUtil.blendColors(firstColor = Color.Black, Color.White, .25f)
)
light.base = givenColor

return light
}
ThemeGenMode.BLEND -> {
val blend = blendColor ?: run { givenColor }
val backgroundColor = generateLightBackgroundColor(blend)
val light = lightColorScheme(
primary = givenColor,
secondary = secondColor,
tertiary = generateLightTertiaryColor(blend),
background = generateLightBackgroundColor(blend),
onPrimary = Color.White,
onSecondary = Color.White
background = backgroundColor,
surface = ColorUtil.blendColors(firstColor = backgroundColor, secondColor = Color.White, .9f),
onPrimary = generateOverTheTopLightColor(givenColor),
onSecondary = generateOverTheTopLightColor(secondColor),
onSurface = ColorUtil.blendColors(firstColor = Color.Black, Color.White, .25f)
)
light.base = blend

Expand All @@ -154,13 +165,18 @@ object ThemeGenUtil {
* @return A dark theme color set. [ColorScheme]
*/
private fun generateThemeDarkColorScheme(givenColor: Color): ColorScheme {
val darkPrimary = generateDarkPrimaryColor(givenColor)
val darkSecondary = generateDarkSecondaryColor(givenColor)
val darkBackground = generateDarkBackgroundColor(givenColor)
val darkColorScheme = darkColorScheme(
primary = generateDarkPrimaryColor(givenColor),
secondary = generateDarkSecondaryColor(givenColor),
primary = darkPrimary,
secondary = darkSecondary,
tertiary = generateDarkTertiaryColor(givenColor),
background = generateDarkBackgroundColor(givenColor),
onPrimary = Color.White,
onSecondary = Color.Black,
background = darkBackground,
surface = ColorUtil.blendColors(firstColor = darkBackground, secondColor = Color.Black, .9f),
onPrimary = ColorUtil.blendColors(firstColor = darkPrimary, secondColor = Color.White, .9f),
onSecondary = ColorUtil.blendColors(firstColor = darkSecondary, secondColor = Color.White, .9f),
onSurface = Color.White
)

darkColorScheme.base = givenColor
Expand All @@ -178,27 +194,37 @@ object ThemeGenUtil {
private fun generateMultiInputThemeDarkColorScheme(givenColor: Color, secondColor: Color, blendColor: Color? = null, themeGenMode: ThemeGenMode = ThemeGenMode.SEQUENCE): ColorScheme {
when (themeGenMode) {
ThemeGenMode.SEQUENCE -> {
val darkPrimary = generateDarkPrimaryColor(givenColor)
val darkSecondary = generateDarkSecondaryColor(secondColor)
val darkBackground = generateDarkBackgroundColor(givenColor)
val dark = darkColorScheme(
primary = generateDarkPrimaryColor(givenColor),
secondary = generateDarkSecondaryColor(secondColor),
primary = darkPrimary,
secondary = darkSecondary,
tertiary = generateDarkTertiaryColor(givenColor),
background = generateDarkBackgroundColor(givenColor),
onPrimary = Color.White,
onSecondary = Color.White,
background = darkBackground,
surface = ColorUtil.blendColors(firstColor = darkBackground, secondColor = Color.Black, .9f),
onPrimary = ColorUtil.blendColors(firstColor = darkPrimary, secondColor = Color.White, .9f),
onSecondary = ColorUtil.blendColors(firstColor = darkSecondary, secondColor = Color.White, .9f),
onSurface = Color.White
)
dark.base = givenColor

return dark
}
ThemeGenMode.BLEND -> {
val blend = blendColor ?: run { givenColor }
val darkPrimary = generateDarkPrimaryColor(givenColor)
val darkSecondary = generateDarkSecondaryColor(secondColor)
val darkBackground = generateDarkBackgroundColor(blend)
val dark = darkColorScheme(
primary = generateDarkPrimaryColor(givenColor),
secondary = generateDarkSecondaryColor(secondColor),
primary = darkPrimary,
secondary = darkSecondary,
tertiary = generateDarkTertiaryColor(blend),
background = generateDarkBackgroundColor(blend),
onPrimary = Color.White,
onSecondary = Color.White,
background = darkBackground,
surface = ColorUtil.blendColors(firstColor = darkBackground, secondColor = Color.Black, .9f),
onPrimary = ColorUtil.blendColors(firstColor = darkPrimary, secondColor = Color.White, .9f),
onSecondary = ColorUtil.blendColors(firstColor = darkSecondary, secondColor = Color.White, .9f),
onSurface = Color.White
)
dark.base = blend

Expand Down Expand Up @@ -229,6 +255,14 @@ object ThemeGenUtil {
return Color.hsl(hue = primaryColor.hsl.hue, saturation = .4f, lightness = .95f)
}

private fun generateOverTheTopLightColor(givenColor: Color): Color {
return if (givenColor.isHighLightColor) {
ColorUtil.blendColors(firstColor = givenColor, Color.Black, .6f)
} else {
ColorUtil.blendColors(firstColor = givenColor, Color.White, .9f)
}
}

/**
* Generate dark primary color for given color.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.kavi.droid.color.palette.color

import androidx.compose.ui.graphics.Color
import com.kavi.droid.color.palette.model.KvColor
import com.google.common.truth.Truth.assertThat
import com.kavi.droid.color.palette.model.ColorCompareResult
import org.junit.Test

class ColorPackageTest {
private val redColor = MatPackage.MatRed
private val greenColor = MatPackage.MatLGreen
private val blueColor = MatPackage.MatLBlue

private val colorPackage = object : ColorPackage() {
override fun getColorList(): List<KvColor> = listOf(redColor, greenColor, blueColor)
}

@Test
fun `getColor returns correct color by name`() {
val result = colorPackage.getColor("MatLGreen")
assertThat(result).isEqualTo(greenColor)
}

@Test
fun `getColor returns MatWhite when color not found`() {
val result = colorPackage.getColor("Purple")
assertThat(result).isEqualTo(MatPackage.MatWhite)
}

@Test
fun `compareColor returns exact match when color exists`() {
val result: ColorCompareResult = colorPackage.compareColor(Color(1f, 0f, 0f)) // Red
assertThat(result.isExactMatch).isFalse()
assertThat(result.matchedColor).isEqualTo(redColor)
assertThat(result.colorDistance).isEqualTo(0.5176471f)
}

@Test
fun `compareColor returns closest color when no exact match`() {
val input = Color(0.9f, 0.1f, 0.1f) // Close to red
val result = colorPackage.compareColor(input)

assertThat(result.isExactMatch).isFalse()
assertThat(result.matchedColor).isEqualTo(redColor)
assertThat(result.colorDistance).isGreaterThan(0f)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.kavi.droid.color.palette.util

import androidx.compose.ui.graphics.Color
import com.kavi.droid.color.palette.color.Mat200Package
import com.kavi.droid.color.palette.color.Mat300Package
import com.kavi.droid.color.palette.color.Mat700Package
import com.kavi.droid.color.palette.color.MatPackage
import com.kavi.droid.color.palette.model.ColorCompareResult
import io.mockk.every
import io.mockk.mockkObject
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import org.junit.Before
import org.junit.Test

class ColorUtilTest {

@Before
fun setupMocks() {
mockkObject(Mat700Package, MatPackage, Mat300Package, Mat200Package, Color)
}

@Test
fun `validateColorHex returns true for valid 6-digit hex`() {
assertTrue(ColorUtil.validateColorHex("#A1B2C3"))
}

@Test
fun `validateColorHex returns true for valid 8-digit hex`() {
assertTrue(ColorUtil.validateColorHex("#FFAABBCC"))
}

@Test
fun `validateColorHex returns false for invalid hex`() {
assertFalse(ColorUtil.validateColorHex("123456"))
assertFalse(ColorUtil.validateColorHex("#XYZ123"))
assertFalse(ColorUtil.validateColorHex("#123"))
}

@Test
fun `getHex returns correct hex string`() {
val color = Color(0.5f, 0.25f, 0.75f)
val hex = ColorUtil.getHex(color)
assertEquals("#8040bf", hex)
}

@Test
fun `getHexWithAlpha returns correct hex string with alpha`() {
val color = Color(0.5f, 0.25f, 0.75f, 0.5f)
val hex = ColorUtil.getHexWithAlpha(color)
assertEquals("#808040bf", hex)
}

@Test
fun `blendColors returns mid color for bias 0_5`() {
val red = Color(1f, 0f, 0f)
val blue = Color(0f, 0f, 1f)
val blended = ColorUtil.blendColors(red, blue, 0.5f)
assertTrue(blended.red > 0f)
assertTrue(blended.blue > 0f)
assertEquals(0f, blended.green, 0.01f)
}

@Test
fun `getColorDistance returns correct distance`() {
val c1 = Color(1f, 0f, 0f, 1f)
val c2 = Color(0f, 1f, 0f, 1f)
val distance = ColorUtil.getColorDistance(c1, c2)
assertEquals(2.0f, distance, 0.01f)
}

@Test
fun `validateAndReviseColorCount returns revised limits`() {
assertEquals(1, ColorUtil.validateAndReviseColorCount(0))
assertEquals(30, ColorUtil.validateAndReviseColorCount(100))
assertEquals(15, ColorUtil.validateAndReviseColorCount(15))
}

@Test
fun `findClosestColor returns exact match from 700`() {
val color = Color.Red
val kvColor = Mat700Package.MatRed
every { Mat700Package.compareColor(color) } returns ColorCompareResult(true, 0f, kvColor)

val result = ColorUtil.findClosestColor(color)
assertEquals(kvColor, result)
}

@Test
fun `findClosestColor returns closest after checking all packages`() {
val givenColor = Mat700Package.MatRed.color
val kvColor = Mat700Package.MatRed

every { Mat700Package.compareColor(any()) } returns ColorCompareResult(false, 0.3f,
Mat700Package.MatRed)
every { MatPackage.compareColor(any()) } returns ColorCompareResult(false, 0.2f,
Mat700Package.MatRed)
every { Mat300Package.compareColor(any()) } returns ColorCompareResult(false, 0.1f,
Mat700Package.MatRed)
every { Mat200Package.compareColor(any()) } returns ColorCompareResult(false, 0.5f,
Mat700Package.MatRed)

val result = ColorUtil.findClosestColor(givenColor)
assertEquals(kvColor, result)
}
}