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 @@ + + + + + +