diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..09b993d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+*.iml
+.gradle
+/local.properties
+/.idea
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..bb1b6aa
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,4 @@
+Version 1.0.0-beta1 *(2019-08-26)*
+----------------------------
+
+Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cb632d5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Winfooz
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..57ef32d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,117 @@
+# WinValidation
+A light-weight android library that can be quickly integrated into any app to use user input validations.
+
+- Annotations based.
+- Auto validation handling.
+- Auto find view by id.
+- Support custom annotations.
+- Support custom confirmation annotations e.g (Password and Confirm password).
+- Support auto validation while typing.
+- Support multi module projects.
+- Support Androidx.
+- Support auto validation for custom views.
+
+## Setup
+[ ![Download](https://api.bintray.com/packages/mnayef95/WinValidation/validation/images/download.svg) ](https://bintray.com/mnayef95/WinValidation/validation/_latestVersion)
+
+```yaml
+repositories {
+ maven {
+ url "https://dl.bintray.com/mnayef95/WinValidation"
+ }
+}
+```
+
+
+Kotlin
+
+```yaml
+implementation 'com.winfooz:validation:{latest-version}'
+kapt 'com.winfooz:validation-compiler:{latest-version}'
+```
+
+
+
+Java
+
+```yaml
+implementation 'com.winfooz:validation:{latest-version}'
+annotationProcessing 'com.winfooz:validation-compiler:{latest-version}'
+```
+
+
+# How to use
+```kotlin
+@Validations
+class TestActivity : Activity() {
+
+ @NotEmpty(R.id.username, messageResId = R.string.err_username_validation)
+ var editTextUsername: TextInputEditText? = null
+
+ @Password(R.id.password, messageResId = R.string.err_password_validation)
+ var editTextPassword: TextInputEditText? = null
+
+ @ConfirmPassword(R.id.confirm_password, messageResId = R.string.err_confirm_password_validation)
+ var editTextConfirmPassword: TextInputEditText? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_test)
+
+ // Don't forget to bind your activity, fragment or view
+ Validator.bind(this)
+ }
+
+ @ValidateOnClick(R.id.buttonActivityMainSubmit)
+ fun onSubmitClicked() {
+ Toast.makeText(this, "Validation succeeded", Toast.LENGTH_SHORT).show()
+ }
+}
+```
+
+# Auto validate while typing
+```kotlin
+@Validations
+class TestActivity : Activity() {
+
+ @AutoValidate
+ @NotEmpty(R.id.username, messageResId = R.string.err_username_validation)
+ var editTextUsername: TextInputEditText? = null
+
+ ....
+}
+```
+## Library Projects
+For library projects add [Butterknife's gradle plugin](https://github.com/JakeWharton/butterknife#library-projects) to your `buildscript`.
+
+```yaml
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
+ }
+}
+```
+
+and then apply it in your module:
+```yaml
+apply plugin: 'com.android.library'
+apply plugin: 'com.jakewharton.butterknife'
+```
+
+Now make sure you use R2 instead of R inside all WinValidation annotations.
+```kotlin
+@Validations
+class TestActivity : Activity() {
+
+ @AutoValidate
+ @NotEmpty(R2.id.username, messageResId = R2.string.err_username_validation)
+ var editTextUsername: TextInputEditText? = null
+
+ ....
+}
+```
+## License
+WinValidation is released under the MIT license. [See LICENSE](https://github.com/Winfooz/WinValidation/blob/master/LICENSE) for details.
diff --git a/annotations/.gitignore b/annotations/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/annotations/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/annotations/build.gradle b/annotations/build.gradle
new file mode 100644
index 0000000..b525e9e
--- /dev/null
+++ b/annotations/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'java-library'
+apply plugin: 'kotlin'
+apply plugin: 'com.novoda.bintray-release'
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "androidx.annotation:annotation:1.1.0"
+}
+
+sourceCompatibility = JavaVersion.VERSION_1_8
+targetCompatibility = JavaVersion.VERSION_1_8
+
+publish {
+ userOrg = 'mnayef95'
+ repoName = "WinValidation"
+ groupId = "com.winfooz"
+ artifactId = POM_ARTIFACT_ID
+ publishVersion = POM_VERSION
+ desc = POM_DESCRIPTION
+ website = 'https://github.com/Winfooz/WinValidation'
+}
diff --git a/annotations/gradle.properties b/annotations/gradle.properties
new file mode 100644
index 0000000..35cbaa1
--- /dev/null
+++ b/annotations/gradle.properties
@@ -0,0 +1,3 @@
+POM_DESCRIPTION=Annotations for use in your project
+POM_ARTIFACT_ID=validation-annotations
+POM_VERSION=1.0.0-beta
diff --git a/annotations/src/main/java/com/winfooz/AnnotationsAutoValidate.kt b/annotations/src/main/java/com/winfooz/AnnotationsAutoValidate.kt
new file mode 100644
index 0000000..48cfcc8
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/AnnotationsAutoValidate.kt
@@ -0,0 +1,9 @@
+package com.winfooz
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+interface AnnotationsAutoValidate
diff --git a/annotations/src/main/java/com/winfooz/AnnotationsErrorHandlingAdapter.kt b/annotations/src/main/java/com/winfooz/AnnotationsErrorHandlingAdapter.kt
new file mode 100644
index 0000000..e6191c3
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/AnnotationsErrorHandlingAdapter.kt
@@ -0,0 +1,9 @@
+package com.winfooz
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+interface AnnotationsErrorHandlingAdapter
diff --git a/annotations/src/main/java/com/winfooz/AnnotationsValidateAdapter.kt b/annotations/src/main/java/com/winfooz/AnnotationsValidateAdapter.kt
new file mode 100644
index 0000000..73255b5
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/AnnotationsValidateAdapter.kt
@@ -0,0 +1,9 @@
+package com.winfooz
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+interface AnnotationsValidateAdapter
diff --git a/annotations/src/main/java/com/winfooz/AnnotationsValidationRule.kt b/annotations/src/main/java/com/winfooz/AnnotationsValidationRule.kt
new file mode 100644
index 0000000..44f31f2
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/AnnotationsValidationRule.kt
@@ -0,0 +1,9 @@
+package com.winfooz
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+interface AnnotationsValidationRule
diff --git a/annotations/src/main/java/com/winfooz/annotations/Adapter.kt b/annotations/src/main/java/com/winfooz/annotations/Adapter.kt
new file mode 100644
index 0000000..b194d71
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/annotations/Adapter.kt
@@ -0,0 +1,14 @@
+package com.winfooz.annotations
+
+import com.winfooz.AnnotationsValidateAdapter
+import kotlin.reflect.KClass
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class Adapter(val value: KClass)
diff --git a/annotations/src/main/java/com/winfooz/annotations/AutoValidate.kt b/annotations/src/main/java/com/winfooz/annotations/AutoValidate.kt
new file mode 100644
index 0000000..a525f4b
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/annotations/AutoValidate.kt
@@ -0,0 +1,11 @@
+package com.winfooz.annotations
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Target(AnnotationTarget.FIELD)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AutoValidate
diff --git a/annotations/src/main/java/com/winfooz/annotations/AutoValidateWith.kt b/annotations/src/main/java/com/winfooz/annotations/AutoValidateWith.kt
new file mode 100644
index 0000000..c9be680
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/annotations/AutoValidateWith.kt
@@ -0,0 +1,14 @@
+package com.winfooz.annotations
+
+import com.winfooz.AnnotationsAutoValidate
+import kotlin.reflect.KClass
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Target(AnnotationTarget.ANNOTATION_CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AutoValidateWith(val value: KClass)
diff --git a/annotations/src/main/java/com/winfooz/annotations/ConfirmWith.kt b/annotations/src/main/java/com/winfooz/annotations/ConfirmWith.kt
new file mode 100644
index 0000000..e6ae379
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/annotations/ConfirmWith.kt
@@ -0,0 +1,13 @@
+package com.winfooz.annotations
+
+import kotlin.reflect.KClass
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Target(AnnotationTarget.ANNOTATION_CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ConfirmWith(val value: KClass)
diff --git a/annotations/src/main/java/com/winfooz/annotations/ErrorHandling.kt b/annotations/src/main/java/com/winfooz/annotations/ErrorHandling.kt
new file mode 100644
index 0000000..0b16edc
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/annotations/ErrorHandling.kt
@@ -0,0 +1,14 @@
+package com.winfooz.annotations
+
+import com.winfooz.AnnotationsErrorHandlingAdapter
+import kotlin.reflect.KClass
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Target(AnnotationTarget.ANNOTATION_CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ErrorHandling(val value: KClass)
diff --git a/annotations/src/main/java/com/winfooz/annotations/Rule.kt b/annotations/src/main/java/com/winfooz/annotations/Rule.kt
new file mode 100644
index 0000000..f54440f
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/annotations/Rule.kt
@@ -0,0 +1,14 @@
+package com.winfooz.annotations
+
+import com.winfooz.AnnotationsValidationRule
+import kotlin.reflect.KClass
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Target(AnnotationTarget.ANNOTATION_CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class Rule(val value: KClass)
diff --git a/annotations/src/main/java/com/winfooz/annotations/ValidateOnClick.kt b/annotations/src/main/java/com/winfooz/annotations/ValidateOnClick.kt
new file mode 100644
index 0000000..1e5be8e
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/annotations/ValidateOnClick.kt
@@ -0,0 +1,13 @@
+package com.winfooz.annotations
+
+import androidx.annotation.IdRes
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Target(AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ValidateOnClick(@IdRes val value: Int)
diff --git a/annotations/src/main/java/com/winfooz/annotations/Validations.kt b/annotations/src/main/java/com/winfooz/annotations/Validations.kt
new file mode 100644
index 0000000..4235151
--- /dev/null
+++ b/annotations/src/main/java/com/winfooz/annotations/Validations.kt
@@ -0,0 +1,11 @@
+package com.winfooz.annotations
+
+/**
+ * Project: WinValidation
+ * Created: Aug 12, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class Validations
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..c97c7b8
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,39 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.1"
+
+ defaultConfig {
+ applicationId "com.winfooz.validation.sample"
+
+ minSdkVersion 21
+ targetSdkVersion 29
+
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.core:core-ktx:1.0.2'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation "com.google.android.material:material:1.1.0-alpha09"
+
+ implementation project(':module1')
+ implementation project(":validation")
+ kapt project(":compiler")
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5747811
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/winfooz/validation/sample/MainActivity.kt b/app/src/main/java/com/winfooz/validation/sample/MainActivity.kt
new file mode 100644
index 0000000..6487868
--- /dev/null
+++ b/app/src/main/java/com/winfooz/validation/sample/MainActivity.kt
@@ -0,0 +1,41 @@
+package com.winfooz.validation.sample
+
+import android.app.Activity
+import android.os.Bundle
+import android.widget.EditText
+import android.widget.Toast
+import com.winfooz.annotations.AutoValidate
+import com.winfooz.annotations.ValidateOnClick
+import com.winfooz.annotations.Validations
+import com.winfooz.validation.Validator
+import com.winfooz.validation.annotations.NotEmpty
+import com.winfooz.validation.annotations.Password
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Validations
+class MainActivity : Activity() {
+
+ @AutoValidate
+ @NotEmpty(R.id.editTextActivityMainUsername, messageResId = R.string.err_username_validation)
+ var editTextUsername: EditText? = null
+
+ @AutoValidate
+ @Password(R.id.editTextActivityMainPassword, messageResId = R.string.err_password_validation)
+ var editTextPassword: EditText? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ Validator.bind(this)
+ }
+
+ @ValidateOnClick(R.id.buttonActivityMainSubmit)
+ fun onSubmitClicked(boolean: Boolean) {
+ Toast.makeText(this, "isValid: $boolean", Toast.LENGTH_SHORT).show()
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..6348baa
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..a0ad202
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..d5bf25a
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..bbd3e02
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c3c3d1f
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #440404
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..051361a
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+ My Application
+ Username must not be empty
+ Password must be at least 8 characters
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..237f423
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..736cd3f
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,26 @@
+buildscript {
+ ext.kotlin_version = '1.3.50'
+
+ repositories {
+ google()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
+ classpath 'com.novoda:bintray-release:0.9.1'
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/compiler/.gitignore b/compiler/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/compiler/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/compiler/build.gradle b/compiler/build.gradle
new file mode 100644
index 0000000..624b386
--- /dev/null
+++ b/compiler/build.gradle
@@ -0,0 +1,34 @@
+import org.gradle.internal.jvm.Jvm
+
+apply plugin: 'java-library'
+apply plugin: 'kotlin'
+apply plugin: 'com.novoda.bintray-release'
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+ // Code generation library for kotlin.
+ implementation 'com.squareup:kotlinpoet:1.3.0'
+
+ // Code generation library for java.
+ implementation 'com.squareup:javapoet:1.11.1'
+
+ compileOnly files(Jvm.current().getToolsJar())
+
+
+ implementation project(":annotations")
+}
+
+sourceCompatibility = JavaVersion.VERSION_1_8
+targetCompatibility = JavaVersion.VERSION_1_8
+
+publish {
+ userOrg = 'mnayef95'
+ repoName = "WinValidation"
+ groupId = "com.winfooz"
+ artifactId = POM_ARTIFACT_ID
+ publishVersion = POM_VERSION
+ desc = POM_DESCRIPTION
+ website = 'https://github.com/Winfooz/WinValidation'
+}
diff --git a/compiler/gradle.properties b/compiler/gradle.properties
new file mode 100644
index 0000000..9f9cfa7
--- /dev/null
+++ b/compiler/gradle.properties
@@ -0,0 +1,3 @@
+POM_DESCRIPTION=For process annotations and generate code
+POM_ARTIFACT_ID=validation-compiler
+POM_VERSION=1.0.0-beta
diff --git a/compiler/src/main/java/com/winfooz/compiler/ClassNames.kt b/compiler/src/main/java/com/winfooz/compiler/ClassNames.kt
new file mode 100644
index 0000000..d6606f0
--- /dev/null
+++ b/compiler/src/main/java/com/winfooz/compiler/ClassNames.kt
@@ -0,0 +1,12 @@
+package com.winfooz.compiler
+
+import com.squareup.kotlinpoet.ClassName
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+val VIEW_CLASS_NAME = ClassName.bestGuess("android.view.View")
+val CONTEXT_CLASS_NAME = ClassName.bestGuess("android.content.Context")
diff --git a/compiler/src/main/java/com/winfooz/compiler/RUtils.kt b/compiler/src/main/java/com/winfooz/compiler/RUtils.kt
new file mode 100644
index 0000000..2358e2c
--- /dev/null
+++ b/compiler/src/main/java/com/winfooz/compiler/RUtils.kt
@@ -0,0 +1,94 @@
+package com.winfooz.compiler
+
+import com.sun.source.util.Trees
+import com.sun.tools.javac.code.Symbol
+import com.sun.tools.javac.tree.JCTree
+import com.sun.tools.javac.tree.TreeScanner
+import java.util.*
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.Element
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class RUtils(trees: Trees) {
+
+ private val rScanner = RScanner()
+ private var trees: Trees? = trees
+
+ fun elementToId(element: Element, annotation: Class<*>, value: Int?): String {
+ val tree = trees?.getTree(element, getMirror(element, annotation)) as? JCTree?
+ rScanner.reset()
+ tree?.accept(rScanner)
+ if (rScanner.resourceIds.isNotEmpty()) {
+ return getId(rScanner.resourceIds.iterator().next())
+ }
+
+ return getId(value to null)
+ }
+
+ fun elementToStringId(element: Element, annotation: Class<*>, value: Int?): String {
+ val tree = trees?.getTree(element, getMirror(element, annotation)) as? JCTree?
+ rScanner.reset()
+ tree?.accept(rScanner)
+ if (rScanner.resourceIds.isNotEmpty()) {
+ val iterator = rScanner.resourceIds.iterator()
+ iterator.next()
+ return getId(iterator.next())
+ }
+
+ return getId(value to null)
+ }
+
+ private fun getMirror(element: Element, annotation: Class<*>): AnnotationMirror? {
+ for (annotationMirror in element.annotationMirrors) {
+ if (annotationMirror.annotationType.toString() == annotation.canonicalName) {
+ return annotationMirror
+ }
+ }
+ return null
+ }
+
+ private fun getId(pair: Pair): String {
+ val (value, rSymbol) = pair
+ return if (rSymbol != null) {
+ "${rSymbol.packge().qualifiedName}.R.${rSymbol.enclClass().name}.${rSymbol.name}"
+ } else {
+ "$value"
+ }
+ }
+
+ private class RScanner : TreeScanner() {
+
+ var resourceIds: MutableList> = mutableListOf()
+
+ override fun visitSelect(jcFieldAccess: JCTree.JCFieldAccess) {
+ val symbol = jcFieldAccess.sym
+ if (symbol.enclosingElement != null && symbol.enclosingElement.enclosingElement != null &&
+ symbol.enclosingElement.enclosingElement.enclClass() != null
+ ) {
+ try {
+ val value = Objects.requireNonNull((symbol as Symbol.VarSymbol).constantValue) as Int
+ resourceIds.add(value to symbol)
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+
+ override fun visitLiteral(jcLiteral: JCTree.JCLiteral) {
+ try {
+ val value = jcLiteral.value as Int
+ resourceIds.add(value to null)
+ } catch (ignored: Exception) {
+ }
+
+ }
+
+ fun reset() {
+ resourceIds.clear()
+ }
+ }
+}
diff --git a/compiler/src/main/java/com/winfooz/compiler/ValidationProcessor.kt b/compiler/src/main/java/com/winfooz/compiler/ValidationProcessor.kt
new file mode 100644
index 0000000..e0a9610
--- /dev/null
+++ b/compiler/src/main/java/com/winfooz/compiler/ValidationProcessor.kt
@@ -0,0 +1,438 @@
+package com.winfooz.compiler
+
+import com.squareup.kotlinpoet.*
+import com.sun.source.util.Trees
+import com.winfooz.annotations.*
+import java.io.IOException
+import javax.annotation.processing.*
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.*
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.MirroredTypeException
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.ElementFilter
+import javax.lang.model.util.Elements
+import javax.tools.Diagnostic
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+class ValidationProcessor : AbstractProcessor() {
+
+ private val addedTypes: MutableList = mutableListOf()
+ private val elements: MutableMap> = mutableMapOf()
+ private val elementUtils: Elements by lazy { processingEnv.elementUtils }
+ private val messager: Messager by lazy { processingEnv.messager }
+ private var rUtils: RUtils? = null
+
+ override fun init(env: ProcessingEnvironment?) {
+ super.init(env)
+ try {
+ rUtils = RUtils(Trees.instance(processingEnv))
+ } catch (ignored: IllegalArgumentException) {
+ }
+ }
+
+ override fun getSupportedAnnotationTypes(): MutableSet {
+ return mutableSetOf(Validations::class.java.canonicalName)
+ }
+
+ override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean {
+ roundEnv.getElementsAnnotatedWith(Validations::class.java).forEach {
+ elements[it as TypeElement] = ElementFilter.fieldsIn(elementUtils.getAllMembers(it))
+ }
+
+ startProcessingElements()
+ return true
+ }
+
+ private fun startProcessingElements() {
+ elements.forEach { entry ->
+ addedTypes.clear()
+
+ val typeElement = entry.key
+ val elements = entry.value
+ val packageName = (typeElement.enclosingElement as PackageElement).qualifiedName.toString()
+ val validationTypeSpec = TypeSpec.classBuilder("${typeElement.simpleName}_Validation")
+ val constructorFunSpec = FunSpec.constructorBuilder()
+ .addParameter(ParameterSpec.builder("target", entry.key.asClassName()).build())
+ .addParameter(ParameterSpec.builder("source", VIEW_CLASS_NAME).build())
+ val validateFunSpec = FunSpec.builder("validate").addModifiers(KModifier.PRIVATE)
+ .returns(Boolean::class.java)
+ .addParameter(ParameterSpec.builder("context", CONTEXT_CLASS_NAME).build())
+ .addParameter(ParameterSpec.builder("target", typeElement.asClassName()).build())
+ .addCode(CodeBlock.of("var isValid = true\n"))
+ val autoValidateFunSpec = FunSpec.builder("handleAutoValidate").addModifiers(KModifier.PRIVATE)
+ .addParameter(ParameterSpec.builder("context", CONTEXT_CLASS_NAME).build())
+ .addParameter(ParameterSpec.builder("target", typeElement.asClassName()).build())
+
+
+ handleOnClick(constructorFunSpec, entry.key)
+ elements.forEach { element ->
+ val annotations = element.annotationMirrors
+ val validationAnnotationMirror = getValidationAnnotation(annotations)
+ val validationAnnotation = validationAnnotationMirror?.annotationType
+ if (validationAnnotation != null) {
+ val autoValidate = element.getAnnotation(AutoValidate::class.java)
+ val rule = getRule(validationAnnotation)
+ if (rule != null) {
+ val errorHandling = getErrorHandling(validationAnnotation)
+ val adapter = getAdapter(validationAnnotation)
+ val confirmWith = getConfirmWith(validationAnnotation)
+ val confirmWithElement = findAndValidateConfirmWith(elements, confirmWith)
+
+ if (autoValidate != null) {
+ startValidateAutoValidateWith(validationAnnotation, element)
+ val autoValidateWith = getAutoValidateWith(validationAnnotation)
+ autoValidateData(
+ element,
+ autoValidateFunSpec,
+ autoValidateWith
+ )
+ }
+
+ addTypeProperty(validationTypeSpec, rule)
+ addTypeProperty(validationTypeSpec, errorHandling)
+ addTypeProperty(validationTypeSpec, adapter)
+ findView(element, validationAnnotationMirror, constructorFunSpec, validationTypeSpec)
+ validateData(
+ validationAnnotation,
+ element,
+ validationTypeSpec,
+ validateFunSpec,
+ rule,
+ errorHandling,
+ confirmWithElement,
+ adapter,
+ validationAnnotationMirror,
+ typeElement
+ )
+ }
+ }
+ }
+
+ try {
+ constructorFunSpec.addCode(CodeBlock.of("handleAutoValidate(source.context, target)\n"))
+ validateFunSpec.addCode(CodeBlock.of("return isValid\n"))
+ validationTypeSpec.addFunction(constructorFunSpec.build())
+ .addFunction(validateFunSpec.build())
+ .addFunction(autoValidateFunSpec.build())
+ val javaFile = FileSpec.builder(packageName, "${typeElement.simpleName}_Validation")
+ .addType(validationTypeSpec.build())
+ .build()
+ javaFile.writeTo(processingEnv.filer)
+ } catch (ignored: IOException) {
+ }
+ }
+ }
+
+ private fun getValidationAnnotation(annotations: List): AnnotationMirror? {
+ return annotations.mapNotNull { if (it.annotationType.asElement().getAnnotation(Rule::class.java) != null) it else null }
+ .firstOrNull()
+ }
+
+ private fun autoValidateData(
+ element: Element,
+ autoValidateFunSpec: FunSpec.Builder,
+ autoValidateWith: TypeMirror?
+ ) {
+ autoValidateFunSpec.beginControlFlow(
+ "val %N_%N = %T() {",
+ element.simpleName.toString(),
+ autoValidateWith.toString().replace(".", "_"),
+ autoValidateWith!!.asTypeName()
+ )
+ autoValidateFunSpec.addCode("validate%N(context, target)\n", element.simpleName.toString().capitalize())
+ autoValidateFunSpec.endControlFlow()
+ autoValidateFunSpec.addCode(
+ CodeBlock.of(
+ "%N_%N.start(%N)\n",
+ element.simpleName.toString(),
+ autoValidateWith.toString().replace(".", "_"),
+ element.simpleName.toString()
+ )
+ )
+ }
+
+ private fun handleOnClick(constructorFunSpec: FunSpec.Builder, type: TypeElement) {
+ val element =
+ elementUtils.getAllMembers(type).firstOrNull { it.getAnnotation(ValidateOnClick::class.java) != null }
+ val validateOnClick = element?.getAnnotation(ValidateOnClick::class.java)
+ constructorFunSpec.addCode(
+ CodeBlock.of(
+ "source.findViewById(%L)",
+ rUtils?.elementToId(element!!, ValidateOnClick::class.java, validateOnClick?.value)
+ ?: validateOnClick?.value
+ )
+ )
+ constructorFunSpec.beginControlFlow(".setOnClickListener")
+ constructorFunSpec.addCode("val isValid = %N(%N.context, target)\n", "validate", "source")
+ if ((element as ExecutableElement).parameters.size == 0) {
+ constructorFunSpec.beginControlFlow("if(isValid)")
+ constructorFunSpec.addCode(CodeBlock.of("target.%N()\n", element.simpleName.toString()))
+ constructorFunSpec.endControlFlow()
+ } else {
+ val paramType = (element.parameters).first().asType()
+ val isPrimitiveBoolean = paramType.toString() == "boolean"
+ val isBoolean = paramType.toString() == "java.lang.Boolean"
+
+ if (isPrimitiveBoolean || isBoolean) {
+ constructorFunSpec.addCode(CodeBlock.of("target.%N(isValid)\n", element.simpleName.toString()))
+ } else {
+ messager.printMessage(
+ Diagnostic.Kind.ERROR,
+ "The method which annotated with @ValidateOnClick should have zero or one Boolean parameter",
+ element,
+ element.annotationMirrors.first()
+ )
+ }
+ }
+ constructorFunSpec.endControlFlow()
+ }
+
+ private fun getRule(declaredType: DeclaredType): TypeMirror? {
+ val rule = declaredType.asElement().getAnnotation(Rule::class.java)
+ return if (rule != null) {
+ return try {
+ elementUtils.getTypeElement(rule.value.java.canonicalName).asType()
+ } catch (e: MirroredTypeException) {
+ e.typeMirror
+ }
+ } else {
+ null
+ }
+ }
+
+ private fun getErrorHandling(declaredType: DeclaredType): TypeMirror? {
+ val errorHandling = declaredType.asElement().getAnnotation(ErrorHandling::class.java)
+ return if (errorHandling != null) {
+ return try {
+ elementUtils.getTypeElement(errorHandling.value.java.canonicalName).asType()
+ } catch (e: MirroredTypeException) {
+ e.typeMirror
+ }
+ } else {
+ null
+ }
+ }
+
+ private fun getAdapter(declaredType: DeclaredType): TypeMirror? {
+ val adapter = declaredType.asElement().getAnnotation(Adapter::class.java)
+ return if (adapter != null) {
+ return try {
+ elementUtils.getTypeElement(adapter.value.java.canonicalName).asType()
+ } catch (e: MirroredTypeException) {
+ e.typeMirror
+ }
+ } else {
+ null
+ }
+ }
+
+ private fun getConfirmWith(declaredType: DeclaredType): TypeMirror? {
+ val rule = declaredType.asElement().getAnnotation(ConfirmWith::class.java)
+ return if (rule != null) {
+ return try {
+ elementUtils.getTypeElement(rule.value.java.canonicalName).asType()
+ } catch (e: MirroredTypeException) {
+ e.typeMirror
+ }
+ } else {
+ null
+ }
+ }
+
+ private fun findAndValidateConfirmWith(elements: MutableList, confirmWith: TypeMirror?): Element? {
+ if (confirmWith == null) {
+ return null
+ }
+ var confirmWithElement: Element? = null
+ elements.forEach { element ->
+ val validateUsing = element.annotationMirrors.map { it.annotationType }
+
+ if (validateUsing.any { it.toString() == confirmWith.toString() }) {
+ confirmWithElement = element
+ }
+ }
+ if (confirmWithElement == null) {
+ messager.printMessage(
+ Diagnostic.Kind.ERROR,
+ "Cannot find any field annotated with @ValidateUsing($confirmWith::class)"
+ )
+ }
+ return confirmWithElement
+ }
+
+ private fun startValidateAutoValidateWith(declaredType: DeclaredType?, element: Element) {
+ val autoValidateWith = declaredType?.asElement()?.getAnnotation(AutoValidateWith::class.java)
+ if (autoValidateWith == null) {
+ messager.printMessage(
+ Diagnostic.Kind.ERROR,
+ "$declaredType should be annotated with @AutoValidateWith if you are using @AutoValidate",
+ element,
+ element.annotationMirrors.first()
+ )
+ }
+ }
+
+ private fun getAutoValidateWith(declaredType: DeclaredType?): TypeMirror? {
+ val autoValidateWith = declaredType?.asElement()?.getAnnotation(AutoValidateWith::class.java)
+ return if (autoValidateWith != null) {
+ return try {
+ elementUtils.getTypeElement(autoValidateWith.value.java.canonicalName).asType()
+ } catch (e: MirroredTypeException) {
+ e.typeMirror
+ }
+ } else {
+ null
+ }
+ }
+
+ private fun addTypeProperty(validationTypeSpec: TypeSpec.Builder, type: TypeMirror?) {
+ val ruleName = type.toString().replace(".", "_")
+ if (!addedTypes.contains(ruleName)) {
+ val ruleProperty = PropertySpec.builder(ruleName, type!!.asTypeName(), KModifier.PRIVATE)
+ .delegate(CodeBlock.of("lazy { %T() }", type.asTypeName()))
+ .build()
+ validationTypeSpec.addProperty(ruleProperty)
+ }
+ addedTypes.add(ruleName)
+ }
+
+ private fun findView(
+ element: Element,
+ declaredType: AnnotationMirror?,
+ constructor: FunSpec.Builder,
+ type: TypeSpec.Builder
+ ) {
+ type.addProperty(
+ PropertySpec.builder(
+ element.simpleName.toString(),
+ element.asType().asTypeName().copy(nullable = true),
+ KModifier.PRIVATE
+ ).build()
+ )
+
+ val idElement = declaredType?.elementValues?.keys?.firstOrNull { it.simpleName.toString() == "value" }
+ val idValue =
+ declaredType?.elementValues?.filter { it.key.simpleName.toString() == "value" }?.values?.firstOrNull()
+ if (idElement?.returnType?.kind != TypeKind.INT) {
+ messager.printMessage(Diagnostic.Kind.ERROR, "The value should be int", element, declaredType)
+ }
+ constructor.addCode(
+ CodeBlock.of(
+ "target.%N = source.findViewById(%L)\n",
+ element.simpleName,
+ rUtils?.elementToId(
+ element,
+ Class.forName(declaredType?.annotationType.toString()),
+ (idValue?.value as Int)
+ ) ?: (idValue?.value as Int)
+ )
+ )
+
+ constructor.addCode(
+ CodeBlock.of(
+ "this.%N = target.%N\n",
+ element.simpleName,
+ element.simpleName
+ )
+ )
+ }
+
+ private fun validateData(
+ declaredType: DeclaredType,
+ element: Element,
+ validationTypeSpec: TypeSpec.Builder,
+ validateFunSpec: FunSpec.Builder,
+ rule: TypeMirror?,
+ errorHandling: TypeMirror?,
+ confirmWithElement: Element?,
+ adapter: TypeMirror?,
+ annotationMirror: AnnotationMirror?,
+ typeElement: TypeElement
+ ) {
+ val messageResIdElement =
+ annotationMirror?.elementValues?.keys?.firstOrNull { it.simpleName.toString() == "messageResId" }
+ val messageResIdValue =
+ annotationMirror?.elementValues?.filter { it.key.simpleName.toString() == "value" }?.values?.firstOrNull()
+ if (messageResIdElement?.returnType?.kind != TypeKind.INT) {
+ messager.printMessage(Diagnostic.Kind.ERROR, "The value should be int", element, annotationMirror)
+ }
+
+ val elementValidationFunSpec = FunSpec.builder("validate${element.simpleName.toString().capitalize()}")
+ .returns(Boolean::class)
+ .addParameter(ParameterSpec.builder("context", CONTEXT_CLASS_NAME).build())
+ .addParameter(ParameterSpec.builder("target", typeElement.asClassName()).build())
+ elementValidationFunSpec.addCode(
+ CodeBlock.of(
+ "val %N = %N.getData(%N)\n",
+ "${element.simpleName}Data",
+ adapter.toString().replace(".", "_"),
+ element.simpleName.toString()
+ )
+ )
+
+ elementValidationFunSpec.addCode(
+ CodeBlock.of(
+ "val annotation = %N.javaClass.getDeclaredField(%S).getAnnotation(%T::class.java)\n",
+ "target",
+ element.simpleName.toString(),
+ declaredType
+ )
+ )
+
+ if (confirmWithElement == null) {
+ elementValidationFunSpec.beginControlFlow(
+ "if (!%N.isValid(%N, annotation))",
+ rule.toString().replace(".", "_"),
+ "${element.simpleName}Data"
+ )
+ } else {
+ elementValidationFunSpec.beginControlFlow(
+ "if (!%N.isValid(%N, %N, annotation))",
+ rule.toString().replace(".", "_"),
+ confirmWithElement.simpleName.toString(),
+ "${element.simpleName}Data"
+ )
+ }
+ elementValidationFunSpec.addCode(
+ CodeBlock.of(
+ "%N.handleError(%N, context.getString(%L), annotation)\n",
+ errorHandling.toString().replace(".", "_"),
+ element.simpleName.toString(),
+ rUtils?.elementToStringId(
+ element,
+ Class.forName(annotationMirror?.annotationType.toString()),
+ messageResIdValue?.value as Int
+ ) ?: messageResIdValue?.value as Int
+ )
+ )
+ elementValidationFunSpec.addCode(CodeBlock.of("return false\n"))
+ elementValidationFunSpec.nextControlFlow("else")
+ elementValidationFunSpec.addCode(
+ CodeBlock.of(
+ "%N.handleError(%N, null, annotation)\n",
+ errorHandling.toString().replace(".", "_"),
+ element.simpleName.toString()
+ )
+ )
+ elementValidationFunSpec.addCode(CodeBlock.of("return true\n"))
+ elementValidationFunSpec.endControlFlow()
+
+ validationTypeSpec.addFunction(elementValidationFunSpec.build())
+
+ validateFunSpec.beginControlFlow(
+ "if (!validate${element.simpleName.toString().capitalize()}(%N, %N))",
+ "context",
+ "target"
+ )
+ validateFunSpec.addCode(CodeBlock.of("isValid = false\n"))
+ validateFunSpec.endControlFlow()
+ }
+}
diff --git a/compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors b/compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors
new file mode 100644
index 0000000..94d0e7b
--- /dev/null
+++ b/compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors
@@ -0,0 +1 @@
+com.winfooz.compiler.ValidationProcessor,aggregating
diff --git a/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..06ecffe
--- /dev/null
+++ b/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+com.winfooz.compiler.ValidationProcessor
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..69831e7
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,6 @@
+org.gradle.jvmargs=-Xmx1536m
+android.useAndroidX=true
+android.enableJetifier=true
+kotlin.code.style=official
+kapt.use.worker.api=true
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..28b12c2
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Aug 25 16:19:29 EEST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/module1/.gitignore b/module1/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/module1/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/module1/build.gradle b/module1/build.gradle
new file mode 100644
index 0000000..48e6e05
--- /dev/null
+++ b/module1/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'com.jakewharton.butterknife'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.1"
+
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 29
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.core:core-ktx:1.0.2'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation "com.google.android.material:material:1.1.0-alpha09"
+
+ implementation project(":validation")
+ kapt project(":compiler")
+}
diff --git a/module1/proguard-rules.pro b/module1/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/module1/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/module1/src/main/AndroidManifest.xml b/module1/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..701c300
--- /dev/null
+++ b/module1/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/module1/src/main/java/com/winfooz/test/TestActivity.kt b/module1/src/main/java/com/winfooz/test/TestActivity.kt
new file mode 100644
index 0000000..a598bec
--- /dev/null
+++ b/module1/src/main/java/com/winfooz/test/TestActivity.kt
@@ -0,0 +1,60 @@
+package com.winfooz.test
+
+import android.app.Activity
+import android.os.Bundle
+import android.widget.CheckBox
+import android.widget.Toast
+import com.google.android.material.textfield.TextInputEditText
+import com.winfooz.annotations.AutoValidate
+import com.winfooz.annotations.ValidateOnClick
+import com.winfooz.annotations.Validations
+import com.winfooz.validation.Validator
+import com.winfooz.validation.annotations.Checked
+import com.winfooz.validation.annotations.ConfirmPassword
+import com.winfooz.validation.annotations.NotEmpty
+import com.winfooz.validation.annotations.Password
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Validations
+class TestActivity : Activity() {
+
+ @AutoValidate
+ @NotEmpty(R2.id.editTextActivityMainUsername, messageResId = R2.string.err_username_validation)
+ var editTextUsername: TextInputEditText? = null
+
+ @AutoValidate
+ @Password(
+ R2.id.editTextActivityMainPassword,
+ messageResId = R2.string.err_password_validation,
+ scheme = Password.Scheme.ALPHA_NUMERIC_ALLOW_SYMBOLS_WITHOUT_SPACE,
+ min = 8
+ )
+ var editTextPassword: TextInputEditText? = null
+
+ @AutoValidate
+ @ConfirmPassword(
+ R2.id.editTextActivityMainConfirmPassword,
+ messageResId = R2.string.err_confirm_password_validation
+ )
+ var editTextConfirmPassword: TextInputEditText? = null
+
+ @AutoValidate
+ @Checked(R2.id.checkBoxActivityMainTermsAndConditions, messageResId = R2.string.err_terms_and_conditions_validation)
+ var checkBoxTermsAndConditions: CheckBox? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_test)
+ Validator.bind(this)
+ }
+
+ @ValidateOnClick(R2.id.buttonActivityMainSubmit)
+ fun onSubmitClicked() {
+ Toast.makeText(this, "Valid", Toast.LENGTH_SHORT).show()
+ }
+}
diff --git a/module1/src/main/res/layout/activity_test.xml b/module1/src/main/res/layout/activity_test.xml
new file mode 100644
index 0000000..789404f
--- /dev/null
+++ b/module1/src/main/res/layout/activity_test.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/module1/src/main/res/values/strings.xml b/module1/src/main/res/values/strings.xml
new file mode 100644
index 0000000..dcf82c2
--- /dev/null
+++ b/module1/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+ Username must not be empty
+ Password must be at least 8 characters
+ Password not match
+ Please check terms and conditions
+
diff --git a/module1/src/main/res/values/styles.xml b/module1/src/main/res/values/styles.xml
new file mode 100644
index 0000000..f2ffd95
--- /dev/null
+++ b/module1/src/main/res/values/styles.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..8a55862
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,4 @@
+include ':app', ':module1'
+include ':compiler'
+include ':annotations'
+include ':validation'
diff --git a/validation/.gitignore b/validation/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/validation/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/validation/build.gradle b/validation/build.gradle
new file mode 100644
index 0000000..2319e4a
--- /dev/null
+++ b/validation/build.gradle
@@ -0,0 +1,38 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'com.novoda.bintray-release'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.1"
+
+
+ defaultConfig {
+ minSdkVersion 21
+ targetSdkVersion 29
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "com.google.android.material:material:1.1.0-alpha09"
+ api project(":annotations")
+}
+
+publish {
+ userOrg = 'mnayef95'
+ repoName = "WinValidation"
+ groupId = "com.winfooz"
+ artifactId = POM_ARTIFACT_ID
+ publishVersion = POM_VERSION
+ desc = POM_DESCRIPTION
+ website = 'https://github.com/Winfooz/WinValidation'
+}
diff --git a/validation/gradle.properties b/validation/gradle.properties
new file mode 100644
index 0000000..128b829
--- /dev/null
+++ b/validation/gradle.properties
@@ -0,0 +1,3 @@
+POM_DESCRIPTION=For use custom annotations
+POM_ARTIFACT_ID=validation
+POM_VERSION=1.0.0-beta
diff --git a/validation/proguard-rules.pro b/validation/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/validation/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/validation/src/main/AndroidManifest.xml b/validation/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1b10765
--- /dev/null
+++ b/validation/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/validation/src/main/java/com/winfooz/validation/AutoValidate.kt b/validation/src/main/java/com/winfooz/validation/AutoValidate.kt
new file mode 100644
index 0000000..d33c1c1
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/AutoValidate.kt
@@ -0,0 +1,18 @@
+package com.winfooz.validation
+
+import android.view.View
+import com.winfooz.AnnotationsAutoValidate
+
+/**
+ * Project: WinValidation
+ * Created: Aug 11, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+typealias OnValueChanged = AutoValidate<*>.() -> Unit
+
+abstract class AutoValidate(val onValueChanged: OnValueChanged) :
+ AnnotationsAutoValidate {
+
+ abstract fun start(view: V?)
+}
diff --git a/validation/src/main/java/com/winfooz/validation/ConfirmValidationRule.kt b/validation/src/main/java/com/winfooz/validation/ConfirmValidationRule.kt
new file mode 100644
index 0000000..aa39c54
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/ConfirmValidationRule.kt
@@ -0,0 +1,15 @@
+package com.winfooz.validation
+
+import android.view.View
+import com.winfooz.AnnotationsValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+interface ConfirmValidationRule : AnnotationsValidationRule {
+
+ fun isValid(view: V?, data: T?, annotation: A?): Boolean
+}
diff --git a/validation/src/main/java/com/winfooz/validation/ErrorHandling.kt b/validation/src/main/java/com/winfooz/validation/ErrorHandling.kt
new file mode 100644
index 0000000..d282d3a
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/ErrorHandling.kt
@@ -0,0 +1,15 @@
+package com.winfooz.validation
+
+import android.view.View
+import com.winfooz.AnnotationsErrorHandlingAdapter
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+interface ErrorHandling : AnnotationsErrorHandlingAdapter {
+
+ fun handleError(view: V?, message: String?, annotation: A?)
+}
diff --git a/validation/src/main/java/com/winfooz/validation/ValidationAdapter.kt b/validation/src/main/java/com/winfooz/validation/ValidationAdapter.kt
new file mode 100644
index 0000000..28c7e37
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/ValidationAdapter.kt
@@ -0,0 +1,15 @@
+package com.winfooz.validation
+
+import android.view.View
+import com.winfooz.AnnotationsValidateAdapter
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+interface ValidationAdapter : AnnotationsValidateAdapter {
+
+ fun getData(view: V?): T?
+}
diff --git a/validation/src/main/java/com/winfooz/validation/ValidationRule.kt b/validation/src/main/java/com/winfooz/validation/ValidationRule.kt
new file mode 100644
index 0000000..43453b6
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/ValidationRule.kt
@@ -0,0 +1,14 @@
+package com.winfooz.validation
+
+import com.winfooz.AnnotationsValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+interface ValidationRule : AnnotationsValidationRule {
+
+ fun isValid(data: T?, annotation: A?): Boolean
+}
diff --git a/validation/src/main/java/com/winfooz/validation/Validator.kt b/validation/src/main/java/com/winfooz/validation/Validator.kt
new file mode 100644
index 0000000..d6ff6e8
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/Validator.kt
@@ -0,0 +1,123 @@
+package com.winfooz.validation
+
+import android.app.Activity
+import android.app.Dialog
+import android.content.Context
+import android.util.Log
+import android.view.View
+import java.lang.reflect.Constructor
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Suppress("UNCHECKED_CAST")
+object Validator {
+
+ private const val TAG = "WinValidation"
+
+ @JvmStatic
+ fun bind(target: Activity) {
+ return bind(target, target.window.decorView)
+ }
+
+ @JvmStatic
+ fun bind(target: View) {
+ return bind(target, target)
+ }
+
+ @JvmStatic
+ fun bind(target: Dialog) {
+ return bind(target, target.window?.decorView)
+ }
+
+ @JvmStatic
+ fun bind(target: Any, source: Activity) {
+ bind(target, source.window.decorView)
+ }
+
+ @JvmStatic
+ fun bind(target: Any, source: Dialog) {
+ bind(target, source.window?.decorView)
+ }
+
+ @JvmStatic
+ fun bind(target: Any, source: View?) {
+ val constructor = findBindingConstructorForClass(target.javaClass)
+ try {
+ constructor?.newInstance(target, source)
+ } catch (e: Exception) {
+ Log.d(TAG, "Unable to instantiate " + target.javaClass.name, e)
+ }
+ }
+
+ @JvmStatic
+ fun isValid(target: Activity): Boolean {
+ return isValid(target, target.window.decorView)
+ }
+
+ @JvmStatic
+ fun isValid(target: View): Boolean {
+ return isValid(target, target)
+ }
+
+ @JvmStatic
+ fun isValid(target: Dialog): Boolean {
+ return isValid(target, target.window?.decorView)
+ }
+
+ @JvmStatic
+ fun isValid(target: Any, source: Activity): Boolean {
+ return isValid(target, source.window.decorView)
+ }
+
+ @JvmStatic
+ fun isValid(target: Any, source: Dialog): Boolean {
+ return isValid(target, source.window?.decorView)
+ }
+
+ @JvmStatic
+ private fun isValid(target: Any, source: View?): Boolean {
+ val constructor = findBindingConstructorForClass(target.javaClass)
+ val validationInstance = constructor?.newInstance(target, source)
+
+ val validateMethod = validationInstance?.javaClass?.getDeclaredMethod(
+ "validate",
+ Context::class.java,
+ target.javaClass
+ )
+ validateMethod?.isAccessible = true
+ return validateMethod?.invoke(validationInstance, source?.context, target) == true
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun findBindingConstructorForClass(cls: Class<*>?): Constructor<*>? {
+ val constructor: Constructor<*>?
+ val clsName = cls?.name
+ constructor = try {
+ val bindingClass = cls?.classLoader?.loadClass(clsName + "_Validation")
+ bindingClass?.getConstructor(cls, View::class.java) as Constructor<*>
+ } catch (e: ClassNotFoundException) {
+ Log.d(
+ TAG,
+ "Unable to find Validation class for " + cls?.name + " trying super class" + cls?.superclass?.name,
+ e
+ )
+ findBindingConstructorForClass(cls?.superclass)
+ } catch (e: NoSuchMethodException) {
+ try {
+ val bindingClass = cls?.classLoader?.loadClass(clsName + "_Validation")
+ bindingClass?.getDeclaredConstructor() as Constructor<*>
+ } catch (e: Exception) {
+ Log.d(TAG, "Unable to find Validation class for " + cls?.name, e)
+ return null
+ }
+ } catch (e: Exception) {
+ Log.d(TAG, "Unable to find Validation class for " + cls?.name, e)
+ return null
+ }
+ return constructor
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/adapters/CheckBoxAdapter.kt b/validation/src/main/java/com/winfooz/validation/adapters/CheckBoxAdapter.kt
new file mode 100644
index 0000000..1c5053d
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/adapters/CheckBoxAdapter.kt
@@ -0,0 +1,17 @@
+package com.winfooz.validation.adapters
+
+import android.widget.CheckBox
+import com.winfooz.validation.ValidationAdapter
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class CheckBoxAdapter : ValidationAdapter {
+
+ override fun getData(view: CheckBox?): Boolean? {
+ return view?.isChecked
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/adapters/EditTextAdapter.kt b/validation/src/main/java/com/winfooz/validation/adapters/EditTextAdapter.kt
new file mode 100644
index 0000000..06dc50b
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/adapters/EditTextAdapter.kt
@@ -0,0 +1,17 @@
+package com.winfooz.validation.adapters
+
+import android.widget.EditText
+import com.winfooz.validation.ValidationAdapter
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class EditTextAdapter : ValidationAdapter {
+
+ override fun getData(view: EditText?): CharSequence? {
+ return view?.text
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/adapters/autovalidate/CheckBoxAutoValidate.kt b/validation/src/main/java/com/winfooz/validation/adapters/autovalidate/CheckBoxAutoValidate.kt
new file mode 100644
index 0000000..92f21a7
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/adapters/autovalidate/CheckBoxAutoValidate.kt
@@ -0,0 +1,21 @@
+package com.winfooz.validation.adapters.autovalidate
+
+import android.widget.CheckBox
+import com.winfooz.validation.AutoValidate
+import com.winfooz.validation.OnValueChanged
+
+/**
+ * Project: WinValidation
+ * Created: Aug 11, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class CheckBoxAutoValidate(onValueChanged: OnValueChanged) :
+ AutoValidate(onValueChanged) {
+
+ override fun start(view: CheckBox?) {
+ view?.setOnCheckedChangeListener { _, _ ->
+ onValueChanged()
+ }
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/adapters/autovalidate/EditTextAutoValidate.kt b/validation/src/main/java/com/winfooz/validation/adapters/autovalidate/EditTextAutoValidate.kt
new file mode 100644
index 0000000..9a7bef7
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/adapters/autovalidate/EditTextAutoValidate.kt
@@ -0,0 +1,46 @@
+package com.winfooz.validation.adapters.autovalidate
+
+import android.widget.EditText
+import com.winfooz.validation.AutoValidate
+import com.winfooz.validation.OnValueChanged
+import com.winfooz.validation.R
+import com.winfooz.validation.utils.onTextChanged
+
+/**
+ * Project: WinValidation
+ * Created: Aug 11, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class EditTextAutoValidate(onValueChanged: OnValueChanged) :
+ AutoValidate(onValueChanged) {
+
+ private lateinit var editText: EditText
+
+ override fun start(view: EditText?) {
+ this.editText = view!!
+ handleEditTextFocusListener()
+ handleEditTextChangeListener()
+ }
+
+ private fun handleEditTextFocusListener() {
+ editText.setTag(R.id.is_first_focus_up, false)
+ editText.setOnFocusChangeListener { _, hasFocus ->
+ if (!hasFocus) {
+ editText.setTag(R.id.is_first_focus_up, true)
+ editText.onFocusChangeListener = null
+ onValueChanged(this)
+ }
+ }
+ editText.isFocusable = true
+ editText.isFocusableInTouchMode = true
+ }
+
+ private fun handleEditTextChangeListener() {
+ editText.onTextChanged {
+ if (editText.getTag(R.id.is_first_focus_up) as Boolean) {
+ onValueChanged(this)
+ }
+ }
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/annotations/Checked.kt b/validation/src/main/java/com/winfooz/validation/annotations/Checked.kt
new file mode 100644
index 0000000..ad4c993
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/annotations/Checked.kt
@@ -0,0 +1,26 @@
+package com.winfooz.validation.annotations
+
+import androidx.annotation.IdRes
+import androidx.annotation.StringRes
+import com.winfooz.annotations.Adapter
+import com.winfooz.annotations.AutoValidateWith
+import com.winfooz.annotations.ErrorHandling
+import com.winfooz.annotations.Rule
+import com.winfooz.validation.adapters.CheckBoxAdapter
+import com.winfooz.validation.adapters.autovalidate.CheckBoxAutoValidate
+import com.winfooz.validation.errorhandlingadapters.CheckBoxErrorHandling
+import com.winfooz.validation.rules.CheckBoxValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Adapter(CheckBoxAdapter::class)
+@AutoValidateWith(CheckBoxAutoValidate::class)
+@Rule(CheckBoxValidationRule::class)
+@ErrorHandling(CheckBoxErrorHandling::class)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD)
+annotation class Checked(@IdRes val value: Int, @StringRes val messageResId: Int)
diff --git a/validation/src/main/java/com/winfooz/validation/annotations/ConfirmEmail.kt b/validation/src/main/java/com/winfooz/validation/annotations/ConfirmEmail.kt
new file mode 100644
index 0000000..628b7ea
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/annotations/ConfirmEmail.kt
@@ -0,0 +1,24 @@
+package com.winfooz.validation.annotations
+
+import androidx.annotation.IdRes
+import androidx.annotation.StringRes
+import com.winfooz.annotations.*
+import com.winfooz.validation.adapters.EditTextAdapter
+import com.winfooz.validation.adapters.autovalidate.EditTextAutoValidate
+import com.winfooz.validation.errorhandlingadapters.EditTextErrorHandling
+import com.winfooz.validation.rules.ConfirmEmailValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Adapter(EditTextAdapter::class)
+@AutoValidateWith(EditTextAutoValidate::class)
+@ConfirmWith(Email::class)
+@Rule(ConfirmEmailValidationRule::class)
+@ErrorHandling(EditTextErrorHandling::class)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD)
+annotation class ConfirmEmail(@IdRes val value: Int, @StringRes val messageResId: Int)
diff --git a/validation/src/main/java/com/winfooz/validation/annotations/ConfirmPassword.kt b/validation/src/main/java/com/winfooz/validation/annotations/ConfirmPassword.kt
new file mode 100644
index 0000000..8ff0a2a
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/annotations/ConfirmPassword.kt
@@ -0,0 +1,24 @@
+package com.winfooz.validation.annotations
+
+import androidx.annotation.IdRes
+import androidx.annotation.StringRes
+import com.winfooz.annotations.*
+import com.winfooz.validation.adapters.EditTextAdapter
+import com.winfooz.validation.adapters.autovalidate.EditTextAutoValidate
+import com.winfooz.validation.errorhandlingadapters.EditTextErrorHandling
+import com.winfooz.validation.rules.ConfirmPasswordValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Adapter(EditTextAdapter::class)
+@AutoValidateWith(EditTextAutoValidate::class)
+@ConfirmWith(Password::class)
+@Rule(ConfirmPasswordValidationRule::class)
+@ErrorHandling(EditTextErrorHandling::class)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD)
+annotation class ConfirmPassword(@IdRes val value: Int, @StringRes val messageResId: Int)
diff --git a/validation/src/main/java/com/winfooz/validation/annotations/Digits.kt b/validation/src/main/java/com/winfooz/validation/annotations/Digits.kt
new file mode 100644
index 0000000..2ef3411
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/annotations/Digits.kt
@@ -0,0 +1,26 @@
+package com.winfooz.validation.annotations
+
+import androidx.annotation.IdRes
+import androidx.annotation.StringRes
+import com.winfooz.annotations.Adapter
+import com.winfooz.annotations.AutoValidateWith
+import com.winfooz.annotations.ErrorHandling
+import com.winfooz.annotations.Rule
+import com.winfooz.validation.adapters.EditTextAdapter
+import com.winfooz.validation.adapters.autovalidate.EditTextAutoValidate
+import com.winfooz.validation.errorhandlingadapters.EditTextErrorHandling
+import com.winfooz.validation.rules.DigitsValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 11, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Adapter(EditTextAdapter::class)
+@AutoValidateWith(EditTextAutoValidate::class)
+@Rule(DigitsValidationRule::class)
+@ErrorHandling(EditTextErrorHandling::class)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD)
+annotation class Digits(@IdRes val value: Int, @StringRes val messageResId: Int)
diff --git a/validation/src/main/java/com/winfooz/validation/annotations/Email.kt b/validation/src/main/java/com/winfooz/validation/annotations/Email.kt
new file mode 100644
index 0000000..d9dc980
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/annotations/Email.kt
@@ -0,0 +1,26 @@
+package com.winfooz.validation.annotations
+
+import androidx.annotation.IdRes
+import androidx.annotation.StringRes
+import com.winfooz.annotations.Adapter
+import com.winfooz.annotations.AutoValidateWith
+import com.winfooz.annotations.ErrorHandling
+import com.winfooz.annotations.Rule
+import com.winfooz.validation.adapters.EditTextAdapter
+import com.winfooz.validation.adapters.autovalidate.EditTextAutoValidate
+import com.winfooz.validation.errorhandlingadapters.EditTextErrorHandling
+import com.winfooz.validation.rules.EmailValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Adapter(EditTextAdapter::class)
+@AutoValidateWith(EditTextAutoValidate::class)
+@Rule(EmailValidationRule::class)
+@ErrorHandling(EditTextErrorHandling::class)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD)
+annotation class Email(@IdRes val value: Int, @StringRes val messageResId: Int)
diff --git a/validation/src/main/java/com/winfooz/validation/annotations/NotBlank.kt b/validation/src/main/java/com/winfooz/validation/annotations/NotBlank.kt
new file mode 100644
index 0000000..22882bf
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/annotations/NotBlank.kt
@@ -0,0 +1,26 @@
+package com.winfooz.validation.annotations
+
+import androidx.annotation.IdRes
+import androidx.annotation.StringRes
+import com.winfooz.annotations.Adapter
+import com.winfooz.annotations.AutoValidateWith
+import com.winfooz.annotations.ErrorHandling
+import com.winfooz.annotations.Rule
+import com.winfooz.validation.adapters.EditTextAdapter
+import com.winfooz.validation.adapters.autovalidate.EditTextAutoValidate
+import com.winfooz.validation.errorhandlingadapters.EditTextErrorHandling
+import com.winfooz.validation.rules.NotBlankValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Adapter(EditTextAdapter::class)
+@AutoValidateWith(EditTextAutoValidate::class)
+@Rule(NotBlankValidationRule::class)
+@ErrorHandling(EditTextErrorHandling::class)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD)
+annotation class NotBlank(@IdRes val value: Int, @StringRes val messageResId: Int)
diff --git a/validation/src/main/java/com/winfooz/validation/annotations/NotEmpty.kt b/validation/src/main/java/com/winfooz/validation/annotations/NotEmpty.kt
new file mode 100644
index 0000000..0c9742c
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/annotations/NotEmpty.kt
@@ -0,0 +1,26 @@
+package com.winfooz.validation.annotations
+
+import androidx.annotation.IdRes
+import androidx.annotation.StringRes
+import com.winfooz.annotations.Adapter
+import com.winfooz.annotations.AutoValidateWith
+import com.winfooz.annotations.ErrorHandling
+import com.winfooz.annotations.Rule
+import com.winfooz.validation.adapters.EditTextAdapter
+import com.winfooz.validation.adapters.autovalidate.EditTextAutoValidate
+import com.winfooz.validation.errorhandlingadapters.EditTextErrorHandling
+import com.winfooz.validation.rules.NotEmptyValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Adapter(EditTextAdapter::class)
+@AutoValidateWith(EditTextAutoValidate::class)
+@Rule(NotEmptyValidationRule::class)
+@ErrorHandling(EditTextErrorHandling::class)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD)
+annotation class NotEmpty(@IdRes val value: Int, @StringRes val messageResId: Int)
diff --git a/validation/src/main/java/com/winfooz/validation/annotations/Password.kt b/validation/src/main/java/com/winfooz/validation/annotations/Password.kt
new file mode 100644
index 0000000..84a6b13
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/annotations/Password.kt
@@ -0,0 +1,45 @@
+package com.winfooz.validation.annotations
+
+import androidx.annotation.IdRes
+import androidx.annotation.StringRes
+import com.winfooz.annotations.Adapter
+import com.winfooz.annotations.AutoValidateWith
+import com.winfooz.annotations.ErrorHandling
+import com.winfooz.annotations.Rule
+import com.winfooz.validation.adapters.EditTextAdapter
+import com.winfooz.validation.adapters.autovalidate.EditTextAutoValidate
+import com.winfooz.validation.errorhandlingadapters.EditTextErrorHandling
+import com.winfooz.validation.rules.PasswordValidationRule
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+@Adapter(EditTextAdapter::class)
+@AutoValidateWith(EditTextAutoValidate::class)
+@Rule(PasswordValidationRule::class)
+@ErrorHandling(EditTextErrorHandling::class)
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD)
+annotation class Password(
+ @IdRes val value: Int,
+ @StringRes val messageResId: Int,
+ val scheme: Scheme = Scheme.ANY,
+ val min: Int = 6
+) {
+
+ enum class Scheme {
+
+ ANY,
+ ALPHA,
+ ALPHA_MIXED_CASE,
+ NUMERIC,
+ ALPHA_NUMERIC,
+ ALPHA_NUMERIC_MIXED_CASE,
+ ALPHA_NUMERIC_SYMBOLS,
+ ALPHA_NUMERIC_MIXED_CASE_SYMBOLS,
+ ALPHA_NUMERIC_ALLOW_SYMBOLS_WITHOUT_SPACE
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/errorhandlingadapters/CheckBoxErrorHandling.kt b/validation/src/main/java/com/winfooz/validation/errorhandlingadapters/CheckBoxErrorHandling.kt
new file mode 100644
index 0000000..43e3377
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/errorhandlingadapters/CheckBoxErrorHandling.kt
@@ -0,0 +1,17 @@
+package com.winfooz.validation.errorhandlingadapters
+
+import android.widget.CheckBox
+import com.winfooz.validation.ErrorHandling
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class CheckBoxErrorHandling : ErrorHandling {
+
+ override fun handleError(view: CheckBox?, message: String?, annotation: Annotation?) {
+ view?.error = message
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/errorhandlingadapters/EditTextErrorHandling.kt b/validation/src/main/java/com/winfooz/validation/errorhandlingadapters/EditTextErrorHandling.kt
new file mode 100644
index 0000000..4638df0
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/errorhandlingadapters/EditTextErrorHandling.kt
@@ -0,0 +1,27 @@
+package com.winfooz.validation.errorhandlingadapters
+
+import android.widget.EditText
+import com.google.android.material.textfield.TextInputLayout
+import com.winfooz.validation.ErrorHandling
+import com.winfooz.validation.R
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class EditTextErrorHandling : ErrorHandling {
+
+ override fun handleError(view: EditText?, message: String?, annotation: Annotation?) {
+ val parent = view?.parent?.parent
+ if (parent is TextInputLayout) {
+ parent.isErrorEnabled = message != null
+ parent.error = message
+ parent.requestLayout()
+ view.setTag(R.id.is_first_focus_up, true)
+ } else {
+ view?.error = message
+ }
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/rules/CheckBoxValidationRule.kt b/validation/src/main/java/com/winfooz/validation/rules/CheckBoxValidationRule.kt
new file mode 100644
index 0000000..ee3c204
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/rules/CheckBoxValidationRule.kt
@@ -0,0 +1,17 @@
+package com.winfooz.validation.rules
+
+import com.winfooz.validation.ValidationRule
+import com.winfooz.validation.annotations.Checked
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class CheckBoxValidationRule : ValidationRule {
+
+ override fun isValid(data: Boolean?, annotation: Checked?): Boolean {
+ return data == true
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/rules/ConfirmEmailValidationRule.kt b/validation/src/main/java/com/winfooz/validation/rules/ConfirmEmailValidationRule.kt
new file mode 100644
index 0000000..eccf0fa
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/rules/ConfirmEmailValidationRule.kt
@@ -0,0 +1,18 @@
+package com.winfooz.validation.rules
+
+import android.widget.EditText
+import com.winfooz.validation.ConfirmValidationRule
+import com.winfooz.validation.annotations.ConfirmEmail
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class ConfirmEmailValidationRule : ConfirmValidationRule {
+
+ override fun isValid(view: EditText?, data: CharSequence?, annotation: ConfirmEmail?): Boolean {
+ return view?.text?.toString() == data?.toString()
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/rules/ConfirmPasswordValidationRule.kt b/validation/src/main/java/com/winfooz/validation/rules/ConfirmPasswordValidationRule.kt
new file mode 100644
index 0000000..4e21fe4
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/rules/ConfirmPasswordValidationRule.kt
@@ -0,0 +1,18 @@
+package com.winfooz.validation.rules
+
+import android.widget.EditText
+import com.winfooz.validation.ConfirmValidationRule
+import com.winfooz.validation.annotations.ConfirmPassword
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class ConfirmPasswordValidationRule : ConfirmValidationRule {
+
+ override fun isValid(view: EditText?, data: CharSequence?, annotation: ConfirmPassword?): Boolean {
+ return view?.text?.toString() == data?.toString()
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/rules/DigitsValidationRule.kt b/validation/src/main/java/com/winfooz/validation/rules/DigitsValidationRule.kt
new file mode 100644
index 0000000..170a022
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/rules/DigitsValidationRule.kt
@@ -0,0 +1,22 @@
+package com.winfooz.validation.rules
+
+import com.winfooz.validation.ValidationRule
+import com.winfooz.validation.annotations.Digits
+
+/**
+ * Project: WinValidation
+ * Created: Aug 11, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class DigitsValidationRule : ValidationRule {
+
+ override fun isValid(data: CharSequence?, annotation: Digits?): Boolean {
+ return data?.matches(DIGITS_REGEX.toRegex()) == true
+ }
+
+ private companion object {
+
+ private const val DIGITS_REGEX = "[0-9]+"
+ }
+}
\ No newline at end of file
diff --git a/validation/src/main/java/com/winfooz/validation/rules/EmailValidationRule.kt b/validation/src/main/java/com/winfooz/validation/rules/EmailValidationRule.kt
new file mode 100644
index 0000000..c3278cc
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/rules/EmailValidationRule.kt
@@ -0,0 +1,26 @@
+package com.winfooz.validation.rules
+
+import com.winfooz.validation.ValidationRule
+import com.winfooz.validation.annotations.Email
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class EmailValidationRule : ValidationRule {
+
+ override fun isValid(data: CharSequence?, annotation: Email?): Boolean {
+ if (data == null) {
+ return false
+ }
+ return Regex(EMAIL_REGEX).matches(data)
+ }
+
+ private companion object {
+
+ private const val EMAIL_REGEX = "[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})+"
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/rules/NotBlankValidationRule.kt b/validation/src/main/java/com/winfooz/validation/rules/NotBlankValidationRule.kt
new file mode 100644
index 0000000..56e43e7
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/rules/NotBlankValidationRule.kt
@@ -0,0 +1,17 @@
+package com.winfooz.validation.rules
+
+import com.winfooz.validation.ValidationRule
+import com.winfooz.validation.annotations.NotBlank
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class NotBlankValidationRule : ValidationRule {
+
+ override fun isValid(data: CharSequence?, annotation: NotBlank?): Boolean {
+ return data != null && data.isNotBlank()
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/rules/NotEmptyValidationRule.kt b/validation/src/main/java/com/winfooz/validation/rules/NotEmptyValidationRule.kt
new file mode 100644
index 0000000..0735ccb
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/rules/NotEmptyValidationRule.kt
@@ -0,0 +1,17 @@
+package com.winfooz.validation.rules
+
+import com.winfooz.validation.ValidationRule
+import com.winfooz.validation.annotations.NotEmpty
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class NotEmptyValidationRule : ValidationRule {
+
+ override fun isValid(data: CharSequence?, annotation: NotEmpty?): Boolean {
+ return data != null && data.isNotEmpty()
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/rules/PasswordValidationRule.kt b/validation/src/main/java/com/winfooz/validation/rules/PasswordValidationRule.kt
new file mode 100644
index 0000000..2e5c39d
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/rules/PasswordValidationRule.kt
@@ -0,0 +1,39 @@
+package com.winfooz.validation.rules
+
+import com.winfooz.validation.ValidationRule
+import com.winfooz.validation.annotations.Password
+import com.winfooz.validation.annotations.Password.Scheme
+
+
+/**
+ * Project: WinValidation
+ * Created: Aug 10, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+class PasswordValidationRule : ValidationRule {
+
+ override fun isValid(data: CharSequence?, annotation: Password?): Boolean {
+ if (data == null || annotation == null) {
+ return false
+ }
+ val hasMinChars = data.length >= annotation.min
+ val matchesScheme = SCHEME_PATTERNS[annotation.scheme]?.toRegex()?.matches(data) == true
+ return hasMinChars && matchesScheme
+ }
+
+ private companion object {
+
+ private val SCHEME_PATTERNS = mapOf(
+ Scheme.ANY to ".+",
+ Scheme.ALPHA to "\\w+",
+ Scheme.ALPHA_MIXED_CASE to "(?=.*[a-z])(?=.*[A-Z]).+",
+ Scheme.NUMERIC to "\\d+",
+ Scheme.ALPHA_NUMERIC to "(?=.*[a-zA-Z])(?=.*[\\d]).+",
+ Scheme.ALPHA_NUMERIC_MIXED_CASE to "(?=.*[a-z])(?=.*[A-Z])(?=.*[\\d]).+",
+ Scheme.ALPHA_NUMERIC_SYMBOLS to "(?=.*[a-zA-Z])(?=.*[\\d])(?=.*([^\\w]|_)).+",
+ Scheme.ALPHA_NUMERIC_MIXED_CASE_SYMBOLS to "(?=.*[a-z])(?=.*[A-Z])(?=.*[\\d])(?=.*([^\\w]|_)).+",
+ Scheme.ALPHA_NUMERIC_ALLOW_SYMBOLS_WITHOUT_SPACE to "^(?=.*[0-9])(?=.*[A-Za-z!@#$%^&*()_+])(?=\\S+$).+$"
+ )
+ }
+}
diff --git a/validation/src/main/java/com/winfooz/validation/utils/Views.kt b/validation/src/main/java/com/winfooz/validation/utils/Views.kt
new file mode 100644
index 0000000..a67e065
--- /dev/null
+++ b/validation/src/main/java/com/winfooz/validation/utils/Views.kt
@@ -0,0 +1,33 @@
+@file:JvmName("ViewUtils")
+
+package com.winfooz.validation.utils
+
+import android.text.Editable
+import android.text.TextWatcher
+import android.widget.EditText
+import com.winfooz.validation.R
+
+/**
+ * Project: TruckingAndroid
+ * Created: Jul 30, 2019
+ *
+ * @author Mohamed Hamdan
+ */
+internal inline fun EditText.onTextChanged(crossinline callback: (text: CharSequence?) -> Unit) {
+ val textWatcher = object : TextWatcher {
+
+ override fun afterTextChanged(s: Editable?) {
+ // No impl
+ }
+
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
+ // No impl
+ }
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ callback(s)
+ }
+ }
+ addTextChangedListener(textWatcher)
+ setTag(R.id.edit_text_watcher, textWatcher)
+}
diff --git a/validation/src/main/res/values/ids.xml b/validation/src/main/res/values/ids.xml
new file mode 100644
index 0000000..e170f53
--- /dev/null
+++ b/validation/src/main/res/values/ids.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+