Skip to content

Commit

Permalink
Merge pull request #14 from Telefonica/appscore/APPS-7094_AddValidati…
Browse files Browse the repository at this point in the history
…onsForInputs

Add email, phone and custom validations for TextInput.
  • Loading branch information
npatarino authored Sep 24, 2020
2 parents 84e2955 + 55a0901 commit e35ea86
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import androidx.annotation.CallSuper
import androidx.fragment.app.Fragment
import com.telefonica.mistica.catalog.R
import com.telefonica.mistica.input.CheckBoxInput
import com.telefonica.mistica.input.DropDownInput
import com.telefonica.mistica.input.TextInput
import com.telefonica.mistica.input.validations.EmailTextInputValidation
import com.telefonica.mistica.input.validations.PhoneTextInputValidation
import com.telefonica.mistica.input.validations.TextInputValidation
import com.telefonica.mistica.input.validations.TextInputValidationResult

class InputsCatalogFragment : Fragment() {

Expand Down Expand Up @@ -42,6 +48,33 @@ class InputsCatalogFragment : Fragment() {
setText(createdSpannableText())
setMovementMethod(LinkMovementMethod.getInstance())
}

val textInputEmail = findViewById<TextInput>(R.id.text_input_email).apply {
setValidation(EmailTextInputValidation("Email invalid"))
}
findViewById<Button>(R.id.button_text_input_email)?.apply {
setOnClickListener {
textInputEmail?.let { textInputEmail.validate() }
}
}

val textInputPhone = findViewById<TextInput>(R.id.text_input_phone).apply {
setValidation(PhoneTextInputValidation("Phone invalid"))
}
findViewById<Button>(R.id.button_text_input_phone)?.apply {
setOnClickListener {
textInputEmail?.let { textInputPhone.validate() }
}
}

val textInputCustom = findViewById<TextInput>(R.id.text_input_custom).apply {
setValidation(customTextInputValidation)
}
findViewById<Button>(R.id.button_text_input_custom)?.apply {
setOnClickListener {
textInputEmail?.let { textInputCustom.validate() }
}
}
}
}

Expand Down Expand Up @@ -90,4 +123,13 @@ class InputsCatalogFragment : Fragment() {
checkBox.cancelPendingInputEvents()
}
}

private val customTextInputValidation = object : TextInputValidation {
override fun isValid(text: String?): TextInputValidationResult =
if (text.equals("Mistica rules")) {
TextInputValidationResult.Success
} else {
TextInputValidationResult.Invalid("Message should be \"Mistica Rules\"")
}
}
}
68 changes: 68 additions & 0 deletions catalog/src/main/res/layout/screen_inputs_catalog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,74 @@
app:inputHint="Email"
app:inputType="email" />

<com.telefonica.mistica.section.SectionTitleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:text="Email Input with validation" />

<com.telefonica.mistica.input.TextInput
android:id="@+id/text_input_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:inputHint="Email"
app:inputType="email" />

<com.telefonica.mistica.button.Button
android:id="@+id/button_text_input_email"
style="@style/AppTheme.Button.DefaultButton.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Validate email" />

<com.telefonica.mistica.section.SectionTitleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:text="Phone Input with validation" />

<com.telefonica.mistica.input.TextInput
android:id="@+id/text_input_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:inputHint="Phone"
app:inputType="phone" />

<com.telefonica.mistica.button.Button
android:id="@+id/button_text_input_phone"
style="@style/AppTheme.Button.DefaultButton.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Validate phone" />

<com.telefonica.mistica.section.SectionTitleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:text="Text Input with custom validation" />

<com.telefonica.mistica.input.TextInput
android:id="@+id/text_input_custom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:inputHint="Custom text" />

<com.telefonica.mistica.button.Button
android:id="@+id/button_text_input_custom"
style="@style/AppTheme.Button.DefaultButton.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Validate" />

<com.telefonica.mistica.section.SectionTitleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Expand Down
21 changes: 21 additions & 0 deletions library/src/main/java/com/telefonica/mistica/input/TextInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import androidx.databinding.adapters.ListenerUtil
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.telefonica.mistica.R
import com.telefonica.mistica.input.validations.NoValidation
import com.telefonica.mistica.input.validations.TextInputValidation
import com.telefonica.mistica.input.validations.TextInputValidationResult

@BindingMethods(
BindingMethod(
Expand Down Expand Up @@ -78,6 +81,8 @@ class TextInput @JvmOverloads constructor(

private lateinit var editTextView: TextInputEditText

private var validation: TextInputValidation = NoValidation()

override fun handleAttrsAndInflateLayout(
attrs: AttributeSet?,
defStyleAttr: Int,
Expand Down Expand Up @@ -175,6 +180,22 @@ class TextInput @JvmOverloads constructor(
editTextView.setOnEditorActionListener(l)
}

fun setValidation(validation: TextInputValidation) {
this.validation = validation
}

fun isValid(): TextInputValidationResult = validation.isValid(text)

fun validate() = when (val result = isValid()) {
TextInputValidationResult.Success -> {
setErrorEnabled(false)
}
is TextInputValidationResult.Invalid -> {
error = result.invalidMessage
setErrorEnabled(true)
}
}

override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? =
editTextView.onCreateInputConnection(outAttrs)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.telefonica.mistica.input.validations

import java.util.regex.Pattern

interface TextInputValidation {
fun isValid(text: String?): TextInputValidationResult
}

sealed class TextInputValidationResult {
object Success : TextInputValidationResult()
class Invalid(val invalidMessage: String) : TextInputValidationResult()
}

class EmailTextInputValidation(private val invalidMessage: String) : TextInputValidation {
override fun isValid(text: String?): TextInputValidationResult =
if (!text.isNullOrBlank() && emailPattern.matcher(text).matches()) {
TextInputValidationResult.Success
} else {
TextInputValidationResult.Invalid(invalidMessage)
}
}

class PhoneTextInputValidation(private val invalidMessage: String) : TextInputValidation {
override fun isValid(text: String?): TextInputValidationResult =
if (!text.isNullOrBlank() && phonePattern.matcher(text).matches()) {
TextInputValidationResult.Success
} else {
TextInputValidationResult.Invalid(invalidMessage)
}

}

class NoValidation() : TextInputValidation {
override fun isValid(text: String?) = TextInputValidationResult.Success
}

private val emailPattern = Pattern.compile(
"""[a-zA-Z0-9\+\.\_\%\-\+]{1,256}\@[a-zA-Z0-9][a-zA-Z0-9\-]{0,64}(\.[a-zA-Z0-9][a-zA-Z0-9\-]{0,25})+"""
)

private val phonePattern: Pattern = Pattern.compile(
"""(\+[0-9]+[\- \.]*)?(\([0-9]+\)[\- \.]*)?([0-9][0-9\- \.]+[0-9])"""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package com.telefonica.mistica.input.validations

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

class TextInputValidationsTest {

@Test
fun `should return Success when validate a valid email`() {
val emailTextInputValidation = givenAnEmailTextInputValidation()

val validationResult = emailTextInputValidation.isValid(validEmail)

assertTrue(validationResult is TextInputValidationResult.Success)
}

@Test
fun `should return Invalid with message when validate an invalid email`() {
val emailTextInputValidation = givenAnEmailTextInputValidation()

val validationResult = emailTextInputValidation.isValid(invalidEmail)

(validationResult as TextInputValidationResult.Invalid).apply {
assertEquals(invalidMessageEmail, this.invalidMessage)
}
}

@Test
fun `should return Invalid with message when validate a null email`() {
val emailTextInputValidation = givenAnEmailTextInputValidation()

val validationResult = emailTextInputValidation.isValid(null)

assertTrue(validationResult is TextInputValidationResult.Invalid)
}

@Test
fun `should return Invalid with message when validate an empty email`() {
val emailTextInputValidation = givenAnEmailTextInputValidation()

val validationResult = emailTextInputValidation.isValid("")

assertTrue(validationResult is TextInputValidationResult.Invalid)
}

@Test
fun `should return Success when validate a valid phone`() {
val phoneTextInputValidation = givenAPhoneTextInputValidation()

val validationResult = phoneTextInputValidation.isValid(validPhone)

assertTrue(validationResult is TextInputValidationResult.Success)
}

@Test
fun `should return Invalid with message when validate an invalid phone`() {
val phoneTextInputValidation = givenAPhoneTextInputValidation()

val validationResult = phoneTextInputValidation.isValid(invalidPhone)

(validationResult as TextInputValidationResult.Invalid).apply {
assertEquals(invalidMessagePhone, this.invalidMessage)
}
}

@Test
fun `should return Invalid with message when validate a null phone`() {
val phoneTextInputValidation = givenAPhoneTextInputValidation()

val validationResult = phoneTextInputValidation.isValid(null)

assertTrue(validationResult is TextInputValidationResult.Invalid)
}

@Test
fun `should return Invalid with message when validate an empty phone`() {
val phoneTextInputValidation = givenAPhoneTextInputValidation()

val validationResult = phoneTextInputValidation.isValid("")

assertTrue(validationResult is TextInputValidationResult.Invalid)
}

@Test
fun `should return Success when validate with a custom validator a valid String`() {
val customTextInputValidation = givenACustomTextInputValidation()

val validationResult = customTextInputValidation.isValid(validReversedText)

assert(validationResult is TextInputValidationResult.Success)
}

@Test
fun `should return Invalid when validate with a custom validator an invalid String`() {
val customTextInputValidation = givenACustomTextInputValidation()

val validationResult = customTextInputValidation.isValid(invalidReversedText)

(validationResult as TextInputValidationResult.Invalid).apply {
assertEquals(invalidReversedTextMessage, this.invalidMessage)
}
}

@Test
fun `should return Invalid when validate with a custom validator a null String`() {
val customTextInputValidation = givenACustomTextInputValidation()

val validationResult = customTextInputValidation.isValid(null)

assertTrue(validationResult is TextInputValidationResult.Invalid)
}

@Test
fun `should return Invalid when validate with a custom validator an empty String`() {
val customTextInputValidation = givenACustomTextInputValidation()

val validationResult = customTextInputValidation.isValid("")

assertTrue(validationResult is TextInputValidationResult.Invalid)
}

private fun givenAnEmailTextInputValidation() = EmailTextInputValidation(invalidMessageEmail)

private fun givenAPhoneTextInputValidation() = PhoneTextInputValidation(invalidMessagePhone)

private fun givenACustomTextInputValidation(): TextInputValidation =
object : TextInputValidation {
override fun isValid(text: String?): TextInputValidationResult =
if (text?.equals(text.reversed()) == true && !text.isNullOrEmpty()) {
TextInputValidationResult.Success
} else {
TextInputValidationResult.Invalid(invalidReversedTextMessage)
}
}

companion object {
const val invalidMessageEmail = "Invalid email"
const val invalidMessagePhone = "Invalid phone"
const val validEmail = "[email protected]"
const val invalidEmail = "email.telefonica.com"
const val validPhone = "666666666"
const val invalidPhone = "66"
const val invalidReversedTextMessage = "Invalid palindrome"
const val validReversedText = "qwertyuiopoiuytrewq"
const val invalidReversedText = "qwertyuiopqwertyuiop"
}

}

0 comments on commit e35ea86

Please sign in to comment.