diff --git a/README.md b/README.md index 6c3bfd7..edf4392 100644 --- a/README.md +++ b/README.md @@ -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") } ```` @@ -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 diff --git a/gradle/version-catalog/libs.versions.toml b/gradle/version-catalog/libs.versions.toml index 99e5428..e7795fe 100644 --- a/gradle/version-catalog/libs.versions.toml +++ b/gradle/version-catalog/libs.versions.toml @@ -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" } @@ -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" } diff --git a/kv-color-palette/build.gradle.kts b/kv-color-palette/build.gradle.kts index bfa2953..ce02c78 100644 --- a/kv-color-palette/build.gradle.kts +++ b/kv-color-palette/build.gradle.kts @@ -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) } diff --git a/kv-color-palette/gradle.properties b/kv-color-palette/gradle.properties index da7a7a6..f30511c 100644 --- a/kv-color-palette/gradle.properties +++ b/kv-color-palette/gradle.properties @@ -1,3 +1,3 @@ kvColorPaletteGroupId=com.github.KvColorPalette kvColorPaletteArtifactId=KvColorPalette-Android -kvColorPaletteVersion=3.1.0 +kvColorPaletteVersion=3.2.0 diff --git a/kv-color-palette/src/main/kotlin/com/kavi/droid/color/palette/util/ThemeGenUtil.kt b/kv-color-palette/src/main/kotlin/com/kavi/droid/color/palette/util/ThemeGenUtil.kt index 32f07bb..871db6f 100644 --- a/kv-color-palette/src/main/kotlin/com/kavi/droid/color/palette/util/ThemeGenUtil.kt +++ b/kv-color-palette/src/main/kotlin/com/kavi/droid/color/palette/util/ThemeGenUtil.kt @@ -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 @@ -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 @@ -119,13 +124,16 @@ 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 @@ -133,13 +141,16 @@ object ThemeGenUtil { } 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 @@ -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 @@ -178,13 +194,18 @@ 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 @@ -192,13 +213,18 @@ object ThemeGenUtil { } 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 @@ -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. */ diff --git a/kv-color-palette/src/test/kotlin/com/kavi/droid/color/palette/color/ColorPackageTest.kt b/kv-color-palette/src/test/kotlin/com/kavi/droid/color/palette/color/ColorPackageTest.kt new file mode 100644 index 0000000..c28b33c --- /dev/null +++ b/kv-color-palette/src/test/kotlin/com/kavi/droid/color/palette/color/ColorPackageTest.kt @@ -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 = 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) + } +} \ No newline at end of file diff --git a/kv-color-palette/src/test/kotlin/com/kavi/droid/color/palette/util/ColorUtilTest.kt b/kv-color-palette/src/test/kotlin/com/kavi/droid/color/palette/util/ColorUtilTest.kt new file mode 100644 index 0000000..d069a6c --- /dev/null +++ b/kv-color-palette/src/test/kotlin/com/kavi/droid/color/palette/util/ColorUtilTest.kt @@ -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) + } +} \ No newline at end of file