Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #124

Merged
merged 16 commits into from
Apr 10, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.github.kakaocup.kakao.chip

import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.View
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.test.espresso.assertion.ViewAssertions
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import io.github.kakaocup.kakao.common.assertions.BaseAssertions
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher

interface ChipAssertions : BaseAssertions {

/**
* Check if Chip has correct icon for [ChipIconType.CHECKED] icon type.
*
* @param resId Drawable resource to be matched (default is -1)
* @param drawable Drawable instance to be matched (default is null)
* @param tintColorId Tint color resource id (default is null)
* @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
*/
fun hasCheckedIcon(
@DrawableRes resId: Int = -1,
drawable: Drawable? = null,
@ColorRes tintColorId: Int? = null,
toBitmap: ((drawable: Drawable) -> Bitmap)? = null,
) = view.check(ViewAssertions.matches(ChipDrawableMatcher(resId, drawable, ChipIconType.CHECKED, tintColorId, toBitmap)))


/**
* Check if Chip has correct icon for [ChipIconType.CHIP] icon type.
*
* @param resId Drawable resource to be matched (default is -1)
* @param drawable Drawable instance to be matched (default is null)
* @param tintColorId Tint color resource id (default is null)
* @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
*/
fun hasChipIcon(
@DrawableRes resId: Int = -1,
drawable: Drawable? = null,
@ColorRes tintColorId: Int? = null,
toBitmap: ((drawable: Drawable) -> Bitmap)? = null,
) = view.check(ViewAssertions.matches(ChipDrawableMatcher(resId, drawable, ChipIconType.CHIP, tintColorId, toBitmap)))

/**
* Check if Chip has correct icon for [ChipIconType.CLOSE] icon type.
*
* @param resId Drawable resource to be matched (default is -1)
* @param drawable Drawable instance to be matched (default is null)
* @param tintColorId Tint color resource id (default is null)
* @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
*/
fun hasCloseIcon(
@DrawableRes resId: Int = -1,
drawable: Drawable? = null,
@ColorRes tintColorId: Int? = null,
toBitmap: ((drawable: Drawable) -> Bitmap)? = null,
) = view.check(ViewAssertions.matches(ChipDrawableMatcher(resId, drawable, ChipIconType.CLOSE, tintColorId, toBitmap)))

/**
* Verify all icons are hidden
*/
fun hasNoIconVisible() = view.check(ViewAssertions.matches(object : TypeSafeMatcher<View>(View::class.java) {
override fun describeTo(desc: Description) {
desc.appendText("without any icons visible")
}

override fun matchesSafely(view: View): Boolean {
val viewAsChip = view as? Chip ?: return false
return (viewAsChip.chipDrawable as? ChipDrawable)?.verifyNoIconVisible() ?: run {
println("Drawable should be ChipDrawable unless implementation has changed")
false
}
}
}))

fun ChipDrawable.verifyNoIconVisible(): Boolean {
return !isCheckedIconVisible && !isChipIconVisible && !isCloseIconVisible
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.github.kakaocup.kakao.chip

import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.os.Build
import android.view.View
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.material.chip.Chip
import io.github.kakaocup.kakao.common.extentions.toBitmap
import io.github.kakaocup.kakao.common.utilities.getResourceColor
import io.github.kakaocup.kakao.common.utilities.getResourceDrawable
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher

/**
* Matches given drawable with current one for the [chipIconType] drawable type.
*
* @param resId Drawable resource to be matched (default is -1)
* @param drawable Drawable instance to be matched (default is null)
* @param chipIconType drawable type to verify against
* @param tintColorId Tint color resource id (default is null)
* @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
*/
class ChipDrawableMatcher(
@DrawableRes private val resId: Int = -1,
private val drawable: Drawable? = null,
private val chipIconType: ChipIconType,
@ColorRes private val tintColorId: Int? = null,
private val toBitmap: ((drawable: Drawable) -> Bitmap)? = null,
) : TypeSafeMatcher<View>(View::class.java) {

override fun describeTo(desc: Description) {
desc.appendText("with drawable id $resId or provided instance for the $chipIconType icon")
}

@Suppress("ReturnCount")
override fun matchesSafely(view: View?): Boolean {
val expectedDrawable: Drawable? = when {
drawable != null -> drawable
resId >= 0 -> getResourceDrawable(resId)?.mutate()
else -> return (view as? Chip)?.chipDrawable == null
}

// Apply backward compatibility wrap and tints if necessary
val finalDrawable = expectedDrawable.processDrawableForDueToSdk() ?: return false

// Compare actual vs expected drawables
return (view as? Chip)?.getChipActualDrawable(chipIconType)?.mutate()?.let { actualDrawable ->
val actualBitmap = toBitmap?.invoke(actualDrawable) ?: actualDrawable.toBitmap()
val expectedBitmap = toBitmap?.invoke(finalDrawable) ?: finalDrawable.toBitmap()
actualBitmap.sameAs(expectedBitmap)
} ?: false
}

private fun Drawable?.processDrawableForDueToSdk() = when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && this != null -> DrawableCompat.wrap(
this
)
.mutate()

Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && tintColorId != null -> this?.apply {
val tintColor = getResourceColor(tintColorId)
setTintList(ColorStateList.valueOf(tintColor))
setTintMode(PorterDuff.Mode.SRC_IN)
}

else -> this
}

private fun Chip.getChipActualDrawable(chipIconType: ChipIconType): Drawable? = when (chipIconType) {
ChipIconType.CHECKED -> checkedIcon?.takeIf { isCheckedIconVisible }
ChipIconType.CHIP -> chipIcon?.takeIf { isChipIconVisible }
ChipIconType.CLOSE -> closeIcon?.takeIf { isCloseIconVisible }
}
}

enum class ChipIconType {
CHECKED,
CHIP,
CLOSE,
}
16 changes: 16 additions & 0 deletions kakao/src/main/kotlin/io/github/kakaocup/kakao/chip/KChip.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.kakaocup.kakao.chip

import android.view.View
import androidx.test.espresso.DataInteraction
import io.github.kakaocup.kakao.check.CheckableActions
import io.github.kakaocup.kakao.check.CheckableAssertions
import io.github.kakaocup.kakao.common.builders.ViewBuilder
import io.github.kakaocup.kakao.common.views.KBaseView
import io.github.kakaocup.kakao.text.TextViewAssertions
import org.hamcrest.Matcher

class KChip : KBaseView<KChip>, CheckableActions, CheckableAssertions, TextViewAssertions, ChipAssertions {
constructor(function: ViewBuilder.() -> Unit) : super(function)
constructor(parent: Matcher<View>, function: ViewBuilder.() -> Unit) : super(parent, function)
constructor(parent: DataInteraction, function: ViewBuilder.() -> Unit) : super(parent, function)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.github.kakaocup.sample

import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import io.github.kakaocup.kakao.screen.Screen.Companion.onScreen
import io.github.kakaocup.sample.screen.ChipsScreen
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4ClassRunner::class)
class ChipsTest {
@Rule
@JvmField
val rule = ActivityScenarioRule(ChipsActivity::class.java)

@Test
fun testCorrectChipsDisplayed() {
onScreen<ChipsScreen> {
chip1 {
isChecked()
hasText("Chip1")
hasCheckedIcon(R.drawable.ic_sentiment_very_satisfied_black_24dp)
}
chip2 {
isNotChecked()
hasText("Chip2")
hasChipIcon(R.drawable.ic_auto_fix_high_24dp)
}
chip3 {
isNotChecked()
hasText("Chip3")
hasCloseIcon(R.drawable.ic_android_black_24dp, tintColorId = android.R.color.black)
}
chip4 {
isNotChecked()
hasText("Chip4")
hasNoIconVisible()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.kakaocup.sample.screen

import io.github.kakaocup.kakao.chip.KChip
import io.github.kakaocup.kakao.screen.Screen
import io.github.kakaocup.sample.R

open class ChipsScreen : Screen<ChipsScreen>() {
val chip1: KChip = KChip { withId(R.id.chip1) }
val chip2: KChip = KChip { withId(R.id.chip2) }
val chip3: KChip = KChip { withId(R.id.chip3) }
val chip4: KChip = KChip { withId(R.id.chip4) }
}
4 changes: 4 additions & 0 deletions sample/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@
android:name="io.github.kakaocup.sample.AnimatedButtonClickActivity"
android:label="Animated Button Clicks Activity"
android:theme="@style/MaterialAppTheme" />
<activity
android:name="io.github.kakaocup.sample.ChipsActivity"
android:label="Chips Activity"
android:theme="@style/MaterialAppTheme" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.github.kakaocup.sample

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class ChipsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chips)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class TestActivity : AppCompatActivity() {
addRoute(R.id.text_input_layout, TextInputLayoutActivity::class.java)
addRoute(R.id.text_activity, TextActivity::class.java)
addRoute(R.id.switchers_button, SwitchersActivity::class.java)
addRoute(R.id.chips, ChipsActivity::class.java)

findViewById<Button>(R.id.snackbar_button).setOnClickListener {
val snackbar = Snackbar.make(
Expand Down
5 changes: 5 additions & 0 deletions sample/src/main/res/drawable/ic_auto_fix_high_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="#FFF" android:pathData="M7.5,5.6L10,7 8.6,4.5 10,2 7.5,3.4 5,2l1.4,2.5L5,7zM19.5,15.4L17,14l1.4,2.5L17,19l2.5,-1.4L22,19l-1.4,-2.5L22,14zM22,2l-2.5,1.4L17,2l1.4,2.5L17,7l2.5,-1.4L22,7l-1.4,-2.5zM14.37,7.29c-0.39,-0.39 -1.02,-0.39 -1.41,0L1.29,18.96c-0.39,0.39 -0.39,1.02 0,1.41l2.34,2.34c0.39,0.39 1.02,0.39 1.41,0L16.7,11.05c0.39,-0.39 0.39,-1.02 0,-1.41l-2.33,-2.35zM13.34,12.78l-2.12,-2.12 2.44,-2.44 2.12,2.12 -2.44,2.44z"/>

</vector>
58 changes: 58 additions & 0 deletions sample/src/main/res/layout/activity_chips.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:chipSpacing="8dp">

<com.google.android.material.chip.Chip
android:id="@+id/chip1"
style="@style/Widget.MaterialComponents.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
android:checked="true"
app:checkedIcon="@drawable/ic_sentiment_very_satisfied_black_24dp"
android:text="Chip1" />

<com.google.android.material.chip.Chip
android:id="@+id/chip2"
style="@style/Base.Widget.MaterialComponents.Chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
app:chipIcon="@drawable/ic_auto_fix_high_24dp"
app:chipIconVisible="true"
app:closeIconVisible="false"
android:text="Chip2" />

<com.google.android.material.chip.Chip
android:id="@+id/chip3"
style="@style/Base.Widget.MaterialComponents.Chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
app:closeIcon="@drawable/ic_android_black_24dp"
app:closeIconTint="@android:color/black"
android:text="Chip3" />

<com.google.android.material.chip.Chip
android:id="@+id/chip4"
style="@style/Base.Widget.MaterialComponents.Chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
app:closeIconVisible="false"
app:checkedIconVisible="false"
app:chipIconVisible="false"
android:text="Chip4" />

</com.google.android.material.chip.ChipGroup>
</LinearLayout>
6 changes: 6 additions & 0 deletions sample/src/main/res/layout/activity_test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@
android:layout_height="wrap_content"
android:text="SWITCHERS"/>

<Button
android:id="@+id/chips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CHIPS"/>

</GridLayout>

<LinearLayout
Expand Down
Loading