diff --git a/build.gradle.kts b/build.gradle.kts index 0f6f417..3b064e9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ plugins { + alias(libs.plugins.cocoapods).apply(false) alias(libs.plugins.multiplatform).apply(false) alias(libs.plugins.compose.compiler).apply(false) alias(libs.plugins.compose).apply(false) diff --git a/gradle.properties b/gradle.properties index f2edd65..d80416c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,6 +17,9 @@ android.nonTransitiveRClass=true kotlin.mpp.androidGradlePluginCompatibility.nowarn=true kotlin.apple.xcodeCompatibility.nowarn=true kotlin.native.ignoreDisabledTargets=true +kotlin.mpp.stability.nowarn=true +kotlin.mpp.enableCInteropCommonization=true +kotlin.mpp.androidSourceSetLayoutVersion=2 #Compose org.jetbrains.compose.experimental.jscanvas.enabled=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d2df647..8c458d7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.0.21-RC" compose = "1.7.0-rc01" -agp = "8.6.1" +agp = "8.7.0" androidx-activityCompose = "1.9.2" androidx-uiTest = "1.7.3" kotlinx-datetime = "0.6.1" @@ -35,6 +35,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-material3 = { group = "androidx.compose.material3", name = "material3" } [plugins] +cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } compose = { id = "org.jetbrains.compose", version.ref = "compose" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 2c35211..a4b76b9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b41..df97d72 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 68530cb..ba00e73 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -10,13 +10,29 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree plugins { alias(libs.plugins.multiplatform) + alias(libs.plugins.android.library) alias(libs.plugins.compose.compiler) alias(libs.plugins.compose) - alias(libs.plugins.android.library) + alias(libs.plugins.cocoapods) alias(libs.plugins.maven.publish) } kotlin { + cocoapods { + version = "1.0.0" + summary = "Yet Another Kotlin COmpose Validation library" + homepage = "https://github.com/chrisjenx/yakcov" + ios.deploymentTarget = "14.1" + framework { + baseName = "shared" + isStatic = true + pod("libPhoneNumber-iOS") +// @OptIn(ExperimentalKotlinGradlePluginApi::class) +// transitiveExport = true + } + } + + applyDefaultHierarchyTemplate() androidTarget { compilations.all { compileTaskProvider { @@ -59,16 +75,17 @@ kotlin { } } - listOf( - iosX64(), - iosArm64(), - iosSimulatorArm64() - ).forEach { - it.binaries.framework { - baseName = "ComposeApp" - isStatic = true - } - } + iosX64() + iosArm64() + iosSimulatorArm64() +// listOf( +// +// ).forEach { +// it.binaries.framework { +// baseName = "Yakcov" +// isStatic = true +// } +// } sourceSets { commonMain.dependencies { @@ -107,6 +124,11 @@ kotlin { } + + //https://kotlinlang.org/docs/native-objc-interop.html#export-of-kdoc-comments-to-generated-objective-c-headers + targets.withType { + compilations["main"].compilerOptions.options.freeCompilerArgs.add("-Xexport-kdoc") + } } android { diff --git a/library/src/androidMain/kotlin/com/chrisjenx/yakcov/Regex.android.kt b/library/src/androidMain/kotlin/com/chrisjenx/yakcov/Regex.android.kt index 7188afc..6192b35 100644 --- a/library/src/androidMain/kotlin/com/chrisjenx/yakcov/Regex.android.kt +++ b/library/src/androidMain/kotlin/com/chrisjenx/yakcov/Regex.android.kt @@ -9,10 +9,10 @@ private val phoneUtil = PhoneNumberUtil.createInstance(ClassPathResourceMetadata /** * Check if is phone number to best ability of each platform. */ -actual fun String?.isPhoneNumber(): Boolean { +actual fun String?.isPhoneNumber(defaultRegion: String?): Boolean { this ?: return false return try { - val result = phoneUtil.parse(this, "US") + val result = phoneUtil.parse(this, defaultRegion?.uppercase()) phoneUtil.isValidNumber(result) } catch (t: Throwable) { t.printStackTrace() diff --git a/library/src/commonMain/kotlin/com/chrisjenx/yakcov/RegexExt.kt b/library/src/commonMain/kotlin/com/chrisjenx/yakcov/RegexExt.kt index 918218e..92117b1 100644 --- a/library/src/commonMain/kotlin/com/chrisjenx/yakcov/RegexExt.kt +++ b/library/src/commonMain/kotlin/com/chrisjenx/yakcov/RegexExt.kt @@ -17,5 +17,8 @@ fun String?.isEmail(): Boolean { /** * Check if is phone number to best ability of each platform. + * + * @param defaultRegion The default region to use if the number is not in international format. + * it's two digits country code. e.g. "US", "GB", "ES" */ -expect fun String?.isPhoneNumber(): Boolean +expect fun String?.isPhoneNumber(defaultRegion: String? = null): Boolean diff --git a/library/src/commonMain/kotlin/com/chrisjenx/yakcov/strings/StringValueValidatorRules.kt b/library/src/commonMain/kotlin/com/chrisjenx/yakcov/strings/StringValueValidatorRules.kt index d7df552..bc80f32 100644 --- a/library/src/commonMain/kotlin/com/chrisjenx/yakcov/strings/StringValueValidatorRules.kt +++ b/library/src/commonMain/kotlin/com/chrisjenx/yakcov/strings/StringValueValidatorRules.kt @@ -120,11 +120,14 @@ data object Email : ValueValidatorRule { } // Phone +/** + * @param defaultRegion the default region to use for phone number validation, ISO 3166-1 alpha-2 code US, GB, ES, etc + */ @Stable -data object Phone : ValueValidatorRule { +data class Phone(val defaultRegion: String = "US") : ValueValidatorRule { override fun validate(value: String): ValidationResult { // only validate if not empty as Required will check if not empty - return if (!value.isPhoneNumber()) { + return if (!value.isPhoneNumber(defaultRegion)) { ResourceValidationResult.error(Res.string.rulePhone) } else { ResourceValidationResult.success() diff --git a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/RegexTest.kt b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.kt similarity index 94% rename from library/src/commonTest/kotlin/com/chrisjenx/yakcov/RegexTest.kt rename to library/src/commonTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.kt index 9c19a48..027df76 100644 --- a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/RegexTest.kt +++ b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.kt @@ -4,7 +4,7 @@ import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue -class RegexTest { +class PhoneNumberTest { @Test fun isPhoneNumber_fail() { diff --git a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt new file mode 100644 index 0000000..a350c00 --- /dev/null +++ b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt @@ -0,0 +1,31 @@ +package com.chrisjenx.yakcov.strings + +import com.chrisjenx.yakcov.ValidationResult.Outcome +import kotlin.test.Test +import kotlin.test.assertEquals + +class PhoneRuleTest { + + @Test + fun phoneNumber_invalid() { + assertEquals(Outcome.ERROR, Phone().validate("43435").outcome()) + } + + @Test + fun phoneNumber_noRegion() { + assertEquals(Outcome.SUCCESS, Phone().validate("6508991234").outcome()) + } + + @Test + fun phoneNumber_wrongRegion() { + // This is a UK number should error for US + assertEquals(Outcome.ERROR, Phone("US").validate("07745973912").outcome()) + assertEquals(Outcome.SUCCESS, Phone("GB").validate("07740973910").outcome()) + } + + @Test + fun phoneNumber_withRegion() { + assertEquals(Outcome.SUCCESS, Phone().validate("+16508991234").outcome()) + } +} + diff --git a/library/src/iosMain/kotlin/com/chrisjenx/yakcov/RegexExt.ios.kt b/library/src/iosMain/kotlin/com/chrisjenx/yakcov/RegexExt.ios.kt index 307cf8f..4092fc0 100644 --- a/library/src/iosMain/kotlin/com/chrisjenx/yakcov/RegexExt.ios.kt +++ b/library/src/iosMain/kotlin/com/chrisjenx/yakcov/RegexExt.ios.kt @@ -10,7 +10,7 @@ import platform.Foundation.matchesInString * Check if is phone number to best ability of each platform. */ @OptIn(ExperimentalForeignApi::class) -actual fun String?.isPhoneNumber(): Boolean { +actual fun String?.isPhoneNumber(defaultRegion: String?): Boolean { if (this.isNullOrBlank()) return false val detector = NSDataDetector(types = NSTextCheckingTypePhoneNumber, error = null) val range = NSMakeRange(0.toULong(), this.length.toULong()) diff --git a/library/src/jsMain/kotlin/com/chrisjenx/yakcov/RegexExt.js.kt b/library/src/jsMain/kotlin/com/chrisjenx/yakcov/RegexExt.js.kt index dfa35f2..7b9394d 100644 --- a/library/src/jsMain/kotlin/com/chrisjenx/yakcov/RegexExt.js.kt +++ b/library/src/jsMain/kotlin/com/chrisjenx/yakcov/RegexExt.js.kt @@ -17,10 +17,10 @@ package com.chrisjenx.yakcov /** * Check if is phone number to best ability of each platform. */ -actual fun String?.isPhoneNumber(): Boolean { +actual fun String?.isPhoneNumber(defaultRegion: String?): Boolean { this ?: return false return try { - val result = parsePhoneNumber(this, "US") + val result = parsePhoneNumber(phone = this, defaultCountry = defaultRegion?.uppercase()) result?.isValid() ?: false } catch (e: Throwable) { false diff --git a/library/src/jvmMain/kotlin/com/chrisjenx/yakcov/Regex.jvm.kt b/library/src/jvmMain/kotlin/com/chrisjenx/yakcov/Regex.jvm.kt index 71b1af4..19a8433 100644 --- a/library/src/jvmMain/kotlin/com/chrisjenx/yakcov/Regex.jvm.kt +++ b/library/src/jvmMain/kotlin/com/chrisjenx/yakcov/Regex.jvm.kt @@ -7,10 +7,10 @@ private val phoneUtil = PhoneNumberUtil.getInstance() /** * Check if is phone number to best ability of each platform. */ -actual fun String?.isPhoneNumber(): Boolean { +actual fun String?.isPhoneNumber(defaultRegion: String?): Boolean { this ?: return false return try { - val result = phoneUtil.parse(this, "US") + val result = phoneUtil.parse(this, defaultRegion?.uppercase()) phoneUtil.isValidNumber(result) } catch (t: Throwable) { false diff --git a/library/src/wasmJsMain/kotlin/com/chrisjenx/yakcov/RegexExt.wasmJs.kt b/library/src/wasmJsMain/kotlin/com/chrisjenx/yakcov/RegexExt.wasmJs.kt index 38a0251..e0136cc 100644 --- a/library/src/wasmJsMain/kotlin/com/chrisjenx/yakcov/RegexExt.wasmJs.kt +++ b/library/src/wasmJsMain/kotlin/com/chrisjenx/yakcov/RegexExt.wasmJs.kt @@ -4,10 +4,10 @@ package com.chrisjenx.yakcov /** * Check if is phone number to best ability of each platform. */ -actual fun String?.isPhoneNumber(): Boolean { +actual fun String?.isPhoneNumber(defaultRegion: String?): Boolean { this ?: return false return try { - val result = parsePhoneNumber(this, "US") + val result = parsePhoneNumber(this, defaultRegion?.uppercase()) result?.isValid() ?: false } catch (e: Throwable) { false