├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable │ │ │ │ ├── ic_home_black_24dp.xml │ │ │ │ ├── ic_dashboard_black_24dp.xml │ │ │ │ ├── ic_notifications_black_24dp.xml │ │ │ │ ├── ic_settings_black_24dp.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── menu │ │ │ │ └── bottom_nav_menu.xml │ │ │ ├── layout │ │ │ │ ├── activity_main.xml │ │ │ │ ├── fragment_login.xml │ │ │ │ ├── fragment_custom_form.xml │ │ │ │ └── fragment_signup.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── layout-land │ │ │ │ └── fragment_signup.xml │ │ ├── java │ │ │ └── dk │ │ │ │ └── nodes │ │ │ │ └── formvalidator │ │ │ │ └── example │ │ │ │ ├── Extensions.kt │ │ │ │ ├── CustomValidator.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── LoginFragment.kt │ │ │ │ ├── SignupFragment.kt │ │ │ │ └── CustomFormFragment.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── dk │ │ │ └── nodes │ │ │ └── formvalidator │ │ │ └── example │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── dk │ │ └── nodes │ │ └── formvalidator │ │ └── example │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── formvalidator ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── dk │ │ │ │ └── nodes │ │ │ │ └── formvalidator │ │ │ │ ├── validators │ │ │ │ ├── password │ │ │ │ │ ├── PasswordStreinght.kt │ │ │ │ │ └── PasswordValidator.kt │ │ │ │ ├── NumberValidator.kt │ │ │ │ ├── TextInputValidator.kt │ │ │ │ ├── RequiredValidator.kt │ │ │ │ ├── EmailValidator.kt │ │ │ │ ├── ValidatorsExtensions.kt │ │ │ │ ├── NameValidator.kt │ │ │ │ ├── CheckboxValidator.kt │ │ │ │ ├── IdenticalValidator.kt │ │ │ │ └── MinLengthValidator.kt │ │ │ │ ├── base │ │ │ │ ├── BaseValidator.kt │ │ │ │ ├── FormLayoutListener.kt │ │ │ │ ├── Bundlable.kt │ │ │ │ ├── FormErrorMessageHandler.kt │ │ │ │ ├── FormErrorMessageResolver.kt │ │ │ │ ├── ValidatableFieldListener.kt │ │ │ │ └── Validatable.kt │ │ │ │ ├── utils │ │ │ │ ├── Logger.kt │ │ │ │ ├── RegexPatterns.kt │ │ │ │ ├── DefaultErrorMessagesResolver.kt │ │ │ │ └── Extensions.kt │ │ │ │ ├── ValidatableCheckbox.kt │ │ │ │ ├── FormLayout.kt │ │ │ │ └── ValidatableEditText.kt │ │ └── res │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── attrs.xml │ ├── test │ │ └── java │ │ │ └── dk │ │ │ └── nodes │ │ │ └── formvalidator │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── dk │ │ └── nodes │ │ └── formvalidator │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro ├── build.gradle └── maven-push.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── encodings.xml ├── code-comments.xml ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── markdown-navigator │ └── profiles_settings.xml ├── vcs.xml ├── dictionaries │ └── romanlevinzon.xml ├── runConfigurations.xml ├── gradle.xml ├── misc.xml └── markdown-navigator.xml ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── gradlew └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /formvalidator/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':formvalidator' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /formvalidator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/form-validator/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/code-comments.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/dictionaries/romanlevinzon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | validatable 5 | 6 | 7 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/password/PasswordStreinght.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators.password 2 | 3 | enum class PasswordStreinght { 4 | None, Weak, Medium, Strong 5 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/base/BaseValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.base 2 | 3 | /** 4 | * Interface to validate generic value 5 | */ 6 | interface BaseValidator { 7 | fun validate(value: T) : Boolean 8 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | /.idea/ 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 31 10:14:11 CEST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/NumberValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators 2 | 3 | class NumberValidator : TextInputValidator() { 4 | override fun validate(value: String): Boolean { 5 | return value.toDoubleOrNull() != null 6 | } 7 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/utils/Logger.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.utils 2 | 3 | import android.util.Log 4 | 5 | object Logger { 6 | private const val TAG = "NodesValidator" 7 | 8 | fun log(message: String) { 9 | Log.d(TAG, message) 10 | } 11 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/TextInputValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators 2 | 3 | import dk.nodes.formvalidator.base.BaseValidator 4 | 5 | /** 6 | * Base Validator to validate text input 7 | */ 8 | abstract class TextInputValidator : BaseValidator -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/RequiredValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators 2 | 3 | class RequiredValidator(private val isRequired: Boolean): TextInputValidator(){ 4 | override fun validate(value: String): Boolean { 5 | return if (isRequired) value.isNotBlank() else true 6 | } 7 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/EmailValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators 2 | 3 | import android.util.Patterns 4 | 5 | class EmailValidator : TextInputValidator() { 6 | override fun validate(value: String): Boolean { 7 | return Patterns.EMAIL_ADDRESS.matcher(value).matches() 8 | } 9 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/ValidatorsExtensions.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators 2 | 3 | fun TextInputValidator(validatorBlock: (String) -> Boolean) = object : TextInputValidator() { 4 | override fun validate(value: String): Boolean { 5 | return validatorBlock.invoke(value) 6 | } 7 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/NameValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators 2 | 3 | import dk.nodes.formvalidator.utils.RegexPatterns 4 | 5 | class NameValidator : TextInputValidator() { 6 | 7 | override fun validate(value: String): Boolean { 8 | return value.matches(Regex(RegexPatterns.Name)) 9 | } 10 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/base/FormLayoutListener.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.base 2 | 3 | /** 4 | * Interface with FormLayout callbacks 5 | */ 6 | interface FormLayoutListener { 7 | 8 | /** 9 | * Called when form's status changes 10 | * @param isValid indicates new form state 11 | */ 12 | fun onFormValidityChanged(isValid: Boolean) 13 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/CheckboxValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators 2 | 3 | import dk.nodes.formvalidator.base.BaseValidator 4 | 5 | class CheckboxValidator(private val isRequired: Boolean) : BaseValidator { 6 | 7 | override fun validate(value: Boolean): Boolean { 8 | return if (isRequired) value else true 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/IdenticalValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators 2 | 3 | import dk.nodes.formvalidator.ValidatableEditText 4 | 5 | class IdenticalValidator(private val validatableEditText: ValidatableEditText) : TextInputValidator() { 6 | override fun validate(value: String): Boolean { 7 | return value == validatableEditText.text?.toString() ?: "" 8 | } 9 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/utils/RegexPatterns.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.utils 2 | 3 | object RegexPatterns { 4 | 5 | 6 | object Password { 7 | const val Medium = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}\$" 8 | const val Strong = "(?=^.{8,}$)(?=.*\\d)(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$" 9 | } 10 | 11 | 12 | const val Name = "([A-Z][a-zA-Z]*)+( [A-Z][a-zA-Z]*)*" 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/dk/nodes/formvalidator/example/Extensions.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.example 2 | 3 | import android.widget.Toast 4 | import androidx.fragment.app.Fragment 5 | import com.google.android.material.snackbar.Snackbar 6 | 7 | 8 | fun Fragment.showToast(message: String) = Toast.makeText (context,message, Toast.LENGTH_SHORT).show() 9 | 10 | fun Fragment.showSnackbar(message: String) = Snackbar.make(view!!, message, Snackbar.LENGTH_SHORT).show() -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dashboard_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/dk/nodes/formvalidator/example/CustomValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.example 2 | 3 | import dk.nodes.formvalidator.validators.TextInputValidator 4 | import java.lang.NumberFormatException 5 | 6 | class CustomValidator : TextInputValidator() { 7 | override fun validate(value: String): Boolean { 8 | return try { 9 | value.length == 4 10 | } catch (nfe: NumberFormatException) { 11 | false 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/test/java/dk/nodes/formvalidator/example/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.example 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/MinLengthValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators 2 | 3 | 4 | class MinLengthValidator(val min: Int? = null) : TextInputValidator() { 5 | 6 | init { 7 | if (min != null && min < 0) throw IllegalArgumentException("Can't assign negative minimal value") 8 | } 9 | 10 | override fun validate(value: String): Boolean { 11 | return if (min == null) true else value.length >= min 12 | } 13 | } -------------------------------------------------------------------------------- /formvalidator/src/test/java/dk/nodes/formvalidator/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FormValidator 3 | Home 4 | Dashboard 5 | Notifications 6 | Login 7 | Signup 8 | Custom Form 9 | 10 | 11 | Hello blank fragment 12 | 13 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/base/Bundlable.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.base 2 | 3 | import android.os.Bundle 4 | 5 | /** 6 | * Interface to handle bundling FormLayout fields 7 | */ 8 | interface Bundlable { 9 | 10 | /** 11 | * Wraps field value into Bundle 12 | * @return bundle with saved state of this field 13 | */ 14 | fun storeToBundle() : Bundle 15 | 16 | /** 17 | * Restores field's state from the bundle 18 | * @param bundle with the saved state 19 | */ 20 | fun restoreFromBundle(bundle: Bundle) 21 | } -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/base/FormErrorMessageHandler.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.base 2 | 3 | import android.view.View 4 | 5 | /** 6 | * An interface that is used to provide a custom action for a validatable view when error occurs 7 | * It can be provided either to the validatable view specifically of for all the views in the FormLayout 8 | * 9 | */ 10 | interface FormErrorMessageHandler { 11 | /** 12 | * @param view - validatable view 13 | * @param message Error message itself 14 | */ 15 | fun onFieldError(view: View, message: String) 16 | } -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/base/FormErrorMessageResolver.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.base 2 | 3 | 4 | /** 5 | * An interface that is used to resolve default error messages for the text validators 6 | * When specific error message is not specified, default implementation of this interface is used to resolve it 7 | */ 8 | interface FormErrorMessageResolver { 9 | 10 | /** 11 | * @param validator - validator thatt caught an error 12 | * @return the error message specific to validator specified 13 | */ 14 | fun resolveValidatorErrorMessage(validator: BaseValidator<*>) : String 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/res/menu/bottom_nav_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 12 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/validators/password/PasswordValidator.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.validators.password 2 | 3 | import dk.nodes.formvalidator.utils.RegexPatterns 4 | import dk.nodes.formvalidator.validators.TextInputValidator 5 | 6 | 7 | class PasswordValidator(val streinght: PasswordStreinght) : TextInputValidator() { 8 | override fun validate(value: String): Boolean { 9 | return when(streinght) { 10 | PasswordStreinght.Weak -> value.length >= 6 11 | PasswordStreinght.Medium -> value.matches(Regex(RegexPatterns.Password.Medium)) 12 | PasswordStreinght.Strong -> value.matches(Regex(RegexPatterns.Password.Strong)) 13 | PasswordStreinght.None -> true 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/dk/nodes/formvalidator/example/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.example 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("dk.nodes.formvalidator.example", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/base/ValidatableFieldListener.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.base 2 | 3 | /** 4 | * interface to provide callbacks for Validatable fields 5 | */ 6 | interface ValidatableFieldListener { 7 | 8 | /** 9 | * Called when field gets a new isValid value 10 | * @param validatable - caller 11 | * @param isValid - new validity value 12 | */ 13 | fun onFieldValidityChanged(validatable: Validatable, isValid: Boolean) 14 | 15 | /** 16 | * Called when user confirms his input 17 | * @param validatable - caller 18 | */ 19 | fun onInputConfirmed(validatable: Validatable) 20 | 21 | /** 22 | * Called when input loses focus 23 | * @param validatable - field that have lost focus 24 | */ 25 | fun onInputLostFocus(validatable: Validatable) 26 | 27 | } -------------------------------------------------------------------------------- /formvalidator/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /formvalidator/src/androidTest/java/dk/nodes/formvalidator/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator; 2 | 3 | import android.content.Context; 4 | import androidx.test.InstrumentationRegistry; 5 | import androidx.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("dk.nodes.formvalidator.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /formvalidator/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | formvalidator 3 | Email is invalid 4 | Password must contain at least 6 symnboks 5 | Password must contain 8 symbols with at least one lowercase letter and one uppercase letter 6 | Password must contain min 8 symbols with at least one lowercase letter, uppercase letter, number and special symbol 7 | Invalid name 8 | Number is invalid 9 | Two fields mismatch 10 | This field is required 11 | Field is invalid 12 | This field must be checked 13 | This field must be at least %d symbols long 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 18 | 19 | 20 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/dk/nodes/formvalidator/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.example 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.fragment.app.Fragment 6 | import kotlinx.android.synthetic.main.activity_main.* 7 | 8 | class MainActivity : AppCompatActivity() { 9 | 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_main) 14 | if (savedInstanceState == null) showFragment(SignupFragment()) 15 | 16 | bottomNavigationView.setOnNavigationItemSelectedListener { 17 | when(it.itemId) { 18 | R.id.menuLogin -> showFragment(LoginFragment()) 19 | R.id.menuSignup -> showFragment(SignupFragment()) 20 | R.id.menuCustom -> showFragment(CustomFormFragment()) 21 | } 22 | true 23 | } 24 | } 25 | 26 | private fun showFragment(fragment: Fragment) { 27 | supportFragmentManager.beginTransaction() 28 | .replace(container.id, fragment, fragment.tag) 29 | .commit() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /formvalidator/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /formvalidator/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android-extensions' 3 | apply plugin: 'kotlin-android' 4 | apply from: 'maven-push.gradle' 5 | 6 | android { 7 | compileSdkVersion 28 8 | 9 | 10 | defaultConfig { 11 | minSdkVersion 21 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0.1" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | implementation 'com.google.android.material:material:1.0.0' 32 | implementation 'androidx.appcompat:appcompat:1.0.2' 33 | implementation "androidx.core:core-ktx:1.0.2" 34 | testImplementation 'junit:junit:4.12' 35 | androidTestImplementation 'androidx.test:runner:1.1.1' 36 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 38 | } 39 | repositories { 40 | mavenCentral() 41 | } 42 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/base/Validatable.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.base 2 | 3 | 4 | interface Validatable { 5 | 6 | var formErrorMessageResolver: FormErrorMessageResolver 7 | 8 | var formErrorMessageHandler: FormErrorMessageHandler? 9 | 10 | 11 | var isRequired: Boolean 12 | 13 | /** 14 | * Validates the the field 15 | * @return true if field is valid, false otherwise 16 | * @param showError indicates whether field should display the error in case of the bad input 17 | */ 18 | fun validate(showError: Boolean = false): Boolean 19 | 20 | /** 21 | * Adds a listener for this field 22 | * @param listenerValidatable listener to add 23 | */ 24 | fun addFieldValidListener(listenerValidatable: ValidatableFieldListener) 25 | 26 | 27 | /** 28 | * Displays a field error message 29 | * @param message - error message to be displayed 30 | */ 31 | fun showError(message: String) 32 | 33 | /** 34 | * Removes error message 35 | */ 36 | fun clearError() 37 | 38 | /** 39 | * Clears the field input 40 | */ 41 | fun clear() 42 | 43 | 44 | /** 45 | * Retrieve validatable field value 46 | * @return Pair with id of the view and value 47 | */ 48 | fun value() : Pair 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/java/dk/nodes/formvalidator/example/LoginFragment.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.example 2 | 3 | 4 | import android.os.Bundle 5 | import androidx.fragment.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.lifecycle.MutableLiveData 10 | import androidx.lifecycle.Observer 11 | import kotlinx.android.synthetic.main.fragment_login.* 12 | 13 | class LoginFragment : Fragment() { 14 | 15 | override fun onCreateView( 16 | inflater: LayoutInflater, container: ViewGroup?, 17 | savedInstanceState: Bundle? 18 | ): View? { 19 | // Inflate the layout for this fragment 20 | return inflater.inflate(R.layout.fragment_login, container, false) 21 | } 22 | 23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 24 | super.onViewCreated(view, savedInstanceState) 25 | setupForm() 26 | } 27 | 28 | private fun setupForm() { 29 | 30 | loginBtn.setOnClickListener { 31 | // Validate all fields 32 | if (loginForm.validateAll()) { 33 | showToast("Proceed with login") 34 | } else { 35 | showToast("Some fields are invalid") 36 | } 37 | } 38 | 39 | MutableLiveData().observe(this, Observer { }) 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "dk.nodes.formvalidator.example" 11 | minSdkVersion 21 12 | targetSdkVersion 28 13 | versionCode 2 14 | versionName "1.0.1" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation project(":formvalidator") 29 | implementation 'androidx.appcompat:appcompat:1.0.2' 30 | implementation 'androidx.core:core-ktx:1.0.2' 31 | implementation 'com.google.android.material:material:1.0.0' 32 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 33 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 34 | testImplementation 'junit:junit:4.12' 35 | androidTestImplementation 'androidx.test:runner:1.1.1' 36 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 37 | } 38 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/utils/DefaultErrorMessagesResolver.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.utils 2 | 3 | import android.content.Context 4 | import dk.nodes.formvalidator.R 5 | import dk.nodes.formvalidator.base.BaseValidator 6 | import dk.nodes.formvalidator.base.FormErrorMessageResolver 7 | import dk.nodes.formvalidator.validators.* 8 | import dk.nodes.formvalidator.validators.password.PasswordStreinght 9 | import dk.nodes.formvalidator.validators.password.PasswordValidator 10 | 11 | /** 12 | * Default implementation of error messages resolver 13 | * @see FormErrorMessageResolver 14 | */ 15 | open class DefaultErrorMessagesResolver(private val context: Context) : FormErrorMessageResolver { 16 | 17 | override fun resolveValidatorErrorMessage(validator: BaseValidator<*>): String { 18 | 19 | if (validator is MinLengthValidator) 20 | return context.getString(R.string.error_min_length, validator.min) 21 | 22 | val resource = when (validator) { 23 | is EmailValidator -> R.string.error_invalid_email 24 | is NumberValidator -> R.string.errror_invalid_number 25 | is PasswordValidator -> handlePasswordError(validator.streinght) 26 | is IdenticalValidator -> R.string.error_mismatch 27 | is RequiredValidator -> R.string.error_required 28 | is NameValidator -> R.string.error_invalid_name 29 | is CheckboxValidator -> R.string.error_checkbox 30 | else -> R.string.error_invalid 31 | 32 | } 33 | return context.getString(resource) 34 | } 35 | 36 | private fun handlePasswordError(passwordStreinght: PasswordStreinght): Int { 37 | return when (passwordStreinght) { 38 | PasswordStreinght.Weak -> R.string.error_invalid_password_weak 39 | PasswordStreinght.Medium -> R.string.error_invalid_password_medium 40 | PasswordStreinght.Strong -> R.string.error_invalid_password_strong 41 | PasswordStreinght.None -> 0 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Android 17 | 18 | 19 | C/C++ 20 | 21 | 22 | Control flow issuesJava 23 | 24 | 25 | CorrectnessLintAndroid 26 | 27 | 28 | GeneralC/C++ 29 | 30 | 31 | IconsUsabilityLintAndroid 32 | 33 | 34 | Java 35 | 36 | 37 | LintAndroid 38 | 39 | 40 | MessagesCorrectnessLintAndroid 41 | 42 | 43 | PerformanceLintAndroid 44 | 45 | 46 | Probable bugsJava 47 | 48 | 49 | Threading issuesJava 50 | 51 | 52 | UsabilityLintAndroid 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/utils/Extensions.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.utils 2 | 3 | import android.content.res.TypedArray 4 | import android.text.Editable 5 | import android.text.TextWatcher 6 | import android.util.AttributeSet 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.EditText 10 | import androidx.core.content.res.getIntOrThrow 11 | import java.lang.Exception 12 | 13 | 14 | internal typealias Action = () -> Unit 15 | 16 | 17 | internal fun ViewGroup.asSequence(): Sequence = object : Sequence { 18 | override fun iterator(): Iterator = object : Iterator { 19 | private var nextValue: View? = null 20 | private var done = false 21 | private var position: Int = 0 22 | 23 | override fun hasNext(): Boolean { 24 | if (nextValue == null && !done) { 25 | nextValue = getChildAt(position) 26 | position++ 27 | if (nextValue == null) done = true 28 | } 29 | return nextValue != null 30 | } 31 | 32 | override fun next(): View { 33 | if (!hasNext()) { 34 | throw NoSuchElementException() 35 | } 36 | val answer = nextValue 37 | nextValue = null 38 | return answer!! 39 | } 40 | } 41 | } 42 | 43 | internal fun EditText.onTextChanged(block: (String) -> Unit) { 44 | addTextChangedListener(object : TextWatcher { 45 | override fun afterTextChanged(p0: Editable?) { 46 | 47 | } 48 | 49 | override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { 50 | } 51 | 52 | override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) { 53 | block.invoke(p0?.toString() ?: "") 54 | } 55 | }) 56 | } 57 | 58 | internal fun TypedArray.getIntOrNull(id: Int) : Int? { 59 | return try { 60 | getIntOrThrow(id) 61 | } catch (e: Exception) { 62 | null 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/dk/nodes/formvalidator/example/SignupFragment.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.example 2 | 3 | 4 | import android.os.Bundle 5 | import android.util.Log 6 | import androidx.fragment.app.Fragment 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import kotlinx.android.synthetic.main.fragment_signup.* 11 | 12 | 13 | class SignupFragment : Fragment() { 14 | 15 | override fun onCreateView( 16 | inflater: LayoutInflater, container: ViewGroup?, 17 | savedInstanceState: Bundle? 18 | ): View? { 19 | // Inflate the layout for this fragment 20 | return inflater.inflate(R.layout.fragment_signup, container, false) 21 | } 22 | 23 | 24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 25 | super.onViewCreated(view, savedInstanceState) 26 | Log.d("SavedState", "Restore State: $savedInstanceState") 27 | if (savedInstanceState != null) { 28 | signupForm.restoreFromBundle(savedInstanceState) 29 | } 30 | setupForm() 31 | } 32 | 33 | private fun setupForm() { 34 | 35 | 36 | // Listen to form validity 37 | signupForm.setFormValidListener { 38 | signupBtn.isEnabled = it 39 | } 40 | 41 | signupBtn.setOnClickListener { 42 | if (signupForm.validateAll()) { 43 | showToast("Create: ${signupForm.retrieveAll().toSignupRequest()}") 44 | } 45 | } 46 | 47 | } 48 | 49 | override fun onSaveInstanceState(outState: Bundle) { 50 | super.onSaveInstanceState(outState) 51 | val bundle = signupForm.retrieveAsBundle() 52 | outState.putAll(bundle) 53 | Log.d("SavedState", "Saved State: $bundle") 54 | } 55 | 56 | 57 | data class SignupRequest(val email: String, val password: String, val terms: Boolean) 58 | 59 | private fun Map.toSignupRequest() : SignupRequest { 60 | return SignupRequest( 61 | email = get(R.id.emailEt) as String, 62 | password = get(R.id.passwordEt) as String, 63 | terms = get(R.id.checkbox) as Boolean 64 | ) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /formvalidator/src/main/java/dk/nodes/formvalidator/ValidatableCheckbox.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.CheckBox 6 | import dk.nodes.formvalidator.base.FormErrorMessageHandler 7 | import dk.nodes.formvalidator.base.FormErrorMessageResolver 8 | import dk.nodes.formvalidator.base.Validatable 9 | import dk.nodes.formvalidator.base.ValidatableFieldListener 10 | import dk.nodes.formvalidator.utils.DefaultErrorMessagesResolver 11 | import dk.nodes.formvalidator.validators.CheckboxValidator 12 | 13 | open class ValidatableCheckbox : CheckBox, Validatable { 14 | 15 | constructor(context: Context) : super(context, null) { 16 | init(null) 17 | } 18 | 19 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { 20 | init(attrs) 21 | } 22 | 23 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 24 | init(attrs) 25 | } 26 | 27 | override var isRequired: Boolean = false 28 | set(value) { 29 | field = value 30 | validator = CheckboxValidator(value) 31 | } 32 | 33 | var errorMessage: String? = null 34 | 35 | 36 | override var formErrorMessageResolver: FormErrorMessageResolver = 37 | DefaultErrorMessagesResolver(context) 38 | 39 | override var formErrorMessageHandler: FormErrorMessageHandler? = null 40 | 41 | private val checkboxListeners: MutableList = mutableListOf() 42 | 43 | private var validator: CheckboxValidator = CheckboxValidator(isRequired) 44 | 45 | private fun init(attrs: AttributeSet?) { 46 | attrs?.let(::initFromAttrs) 47 | setOnCheckedChangeListener { _, _ -> 48 | val isValid = validate() 49 | checkboxListeners.forEach { it.onFieldValidityChanged(this, isValid) } 50 | } 51 | } 52 | 53 | private fun initFromAttrs(attributeSet: AttributeSet) { 54 | val attrs = context.obtainStyledAttributes(attributeSet, R.styleable.ValidatableCheckbox, 0, 0) 55 | isRequired = attrs.getBoolean(R.styleable.ValidatableCheckbox_required, false) 56 | errorMessage = attrs.getString(R.styleable.ValidatableCheckbox_errorMessage) 57 | attrs.recycle() 58 | } 59 | 60 | 61 | override fun validate(showError: Boolean): Boolean { 62 | val isValid = validator.validate(isChecked) 63 | if (showError && !isValid) { 64 | showError(errorMessage ?: formErrorMessageResolver.resolveValidatorErrorMessage(validator)) 65 | } 66 | return isValid 67 | } 68 | 69 | override fun addFieldValidListener(listenerValidatable: ValidatableFieldListener) { 70 | checkboxListeners.add(listenerValidatable) 71 | } 72 | 73 | override fun showError(message: String) { 74 | formErrorMessageHandler?.onFieldError(this, message) 75 | } 76 | 77 | override fun clearError() { 78 | 79 | } 80 | 81 | override fun clear() { 82 | isChecked = false 83 | } 84 | 85 | override fun value(): Pair { 86 | return id to isChecked 87 | } 88 | } -------------------------------------------------------------------------------- /formvalidator/maven-push.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'signing' 2 | apply plugin: 'maven' 3 | apply plugin: 'io.codearte.nexus-staging' 4 | 5 | // Username & password for Sonatype, stored in gradle.properties 6 | def _ossrhUsername = System.getenv('NEXUS_USERNAME') 7 | def _ossrhPassword = System.getenv('NEXUS_PASSWORD') 8 | 9 | allprojects { ext."signing.keyId" = System.getenv('keyId') } 10 | allprojects { ext."signing.secretKeyRingFile" = System.getenv('secretKeyRingFile') } 11 | allprojects { ext."signing.password" = System.getenv('password') } 12 | 13 | // Artifact settings 14 | def _group = 'dk.nodes.formvalidator' 15 | def _version = '1.0.1' 16 | def _archivesBaseName = 'base' 17 | 18 | def _name = 'Nodes Form Validation Library' 19 | def _description = 'Nodes Form Validation Library' 20 | 21 | nexusStaging { 22 | packageGroup = "dk.nodes" 23 | } 24 | 25 | afterEvaluate { project -> 26 | uploadArchives { 27 | repositories { 28 | mavenDeployer { 29 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 30 | 31 | pom.groupId = _group 32 | pom.artifactId = _archivesBaseName 33 | pom.version = _version 34 | 35 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 36 | authentication(userName: _ossrhUsername, password: _ossrhPassword) 37 | } 38 | 39 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 40 | authentication(userName: _ossrhUsername, password: _ossrhPassword) 41 | } 42 | 43 | pom.project { 44 | name _name 45 | packaging 'aar' 46 | description _description 47 | url 'https://github.dk.nodes-android/form-validator' 48 | inceptionYear '2019' 49 | 50 | scm { 51 | url 'https://github.dk.nodes-android/form-validator' 52 | connection 'scm:https://github.dk.nodes-android/form-validator.git' 53 | } 54 | 55 | licenses { 56 | license { 57 | name 'The Apache License, Version 2.0' 58 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 59 | } 60 | } 61 | 62 | developers { 63 | developer { 64 | id 'johsoe' 65 | name 'Johnny Sørensen' 66 | email 'joso@nodes.dk' 67 | } 68 | } 69 | 70 | issueManagement { 71 | system 'GitHub issues' 72 | url 'https://github.dk.nodes-android/form-validator/issues' 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | signing { 80 | required { gradle.taskGraph.hasTask("uploadArchives") } 81 | sign configurations.archives 82 | } 83 | 84 | task androidSourcesJar(type: Jar) { 85 | classifier = 'sources' 86 | from android.sourceSets.main.java.sourceFiles 87 | } 88 | 89 | artifacts { 90 | archives androidSourcesJar 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /app/src/main/java/dk/nodes/formvalidator/example/CustomFormFragment.kt: -------------------------------------------------------------------------------- 1 | package dk.nodes.formvalidator.example 2 | 3 | 4 | import android.app.AlertDialog 5 | import android.os.Bundle 6 | import androidx.fragment.app.Fragment 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import dk.nodes.formvalidator.FormLayout 11 | import dk.nodes.formvalidator.base.BaseValidator 12 | import dk.nodes.formvalidator.base.FormErrorMessageHandler 13 | import dk.nodes.formvalidator.base.FormErrorMessageResolver 14 | import dk.nodes.formvalidator.validators.TextInputValidator 15 | import kotlinx.android.synthetic.main.fragment_custom_form.* 16 | 17 | /** 18 | * A simple [Fragment] subclass. 19 | */ 20 | class CustomFormFragment : Fragment(), FormErrorMessageHandler, FormErrorMessageResolver{ 21 | 22 | override fun onCreateView( 23 | inflater: LayoutInflater, container: ViewGroup?, 24 | savedInstanceState: Bundle? 25 | ): View? { 26 | // Inflate the layout for this fragment 27 | return inflater.inflate(R.layout.fragment_custom_form, container, false) 28 | } 29 | 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | setupForm() 34 | setupListeners() 35 | } 36 | 37 | private fun setupListeners() { 38 | 39 | radioGroups.setOnCheckedChangeListener { radioGroup, i -> 40 | when(i) { 41 | R.id.modeFocus -> customForm.errorHandlerMode = FormLayout.ErrorHandlerMode.Focus 42 | R.id.modeIme -> customForm.errorHandlerMode = FormLayout.ErrorHandlerMode.Ime 43 | R.id.modeManual -> customForm.errorHandlerMode = FormLayout.ErrorHandlerMode.Manual 44 | } 45 | } 46 | 47 | submitBtn.setOnClickListener { 48 | if (customForm.validateAll()) { 49 | showToast("Looks fine!") 50 | } else { 51 | showToast("Somethings wrong") 52 | } 53 | } 54 | 55 | customFormClearBtn.setOnClickListener { 56 | customForm.clear() 57 | } 58 | } 59 | 60 | private fun setupForm() { 61 | 62 | customForm.setFormValidListener { 63 | submitBtn.isEnabled = it 64 | } 65 | 66 | // Custom Validators 67 | validatableEt1.validator = TextInputValidator { it.length == 5 } 68 | 69 | 70 | // Custom Error Messages 71 | validatableEt1.errorMessage = "Totally wrong" 72 | validatableEt2.requiredMessage = "Required, doctor's orders" 73 | 74 | validatableEt2.validator = CustomValidator() 75 | 76 | customForm.setErrorMessagesHandler(this) 77 | customForm.setErrorMessageResolver(this) 78 | 79 | validatableEt3.setErrorHandler { 80 | // Act when field error received for this field 81 | AlertDialog.Builder(context) 82 | .setTitle("Error") 83 | .setMessage(it) 84 | .show() 85 | } 86 | 87 | } 88 | 89 | override fun onFieldError(view: View, message: String) { 90 | when(view.id) { 91 | R.id.validatableEt1 -> showToast(message) 92 | R.id.validatableEt2 -> showSnackbar(message) 93 | else -> AlertDialog.Builder(context).setTitle("Error").setMessage(message).show() 94 | } 95 | } 96 | 97 | override fun resolveValidatorErrorMessage(validator: BaseValidator<*>): String { 98 | return "This is custom error" 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | xmlns:android 20 | ^$ 21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 | xmlns:.* 30 | ^$ 31 | 32 | 33 | BY_NAME 34 | 35 |
36 |
37 | 38 | 39 | 40 | .*:id 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | http://schemas.android.com/apk/res/android 52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | 60 | name 61 | ^$ 62 | 63 | 64 | 65 |
66 |
67 | 68 | 69 | 70 | style 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | ^$ 82 | 83 | 84 | BY_NAME 85 | 86 |
87 |
88 | 89 | 90 | 91 | .* 92 | http://schemas.android.com/apk/res/android 93 | 94 | 95 | ANDROID_ATTRIBUTE_ORDER 96 | 97 |
98 |
99 | 100 | 101 | 102 | .* 103 | .* 104 | 105 | 106 | BY_NAME 107 | 108 |
109 |
110 |
111 |
112 | 113 | 115 |
116 |
-------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 23 | 24 | 33 | 34 | 45 | 46 | 51 | 52 | 53 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 72 | 73 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |