diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8c458d7..0d28982 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -14,25 +14,26 @@ junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.6"
composeBom = "2024.09.03"
+startupRuntime = "1.2.0"
[libraries]
androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
-androidx-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-uiTest" }
-androidx-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-uiTest" }
-kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
-libphonenumber-jvm = { module = "com.googlecode.libphonenumber:libphonenumber", version.ref = "libphonenumberJvm" }
-libphonenumber-android = { module = "io.michaelrocks:libphonenumber-android", version.ref = "libphonenumberAndroid" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
-junit = { group = "junit", name = "junit", version.ref = "junit" }
-androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-uiTest" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
-androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
+androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startupRuntime" }
+androidx-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-uiTest" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
-androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
+libphonenumber-jvm = { module = "com.googlecode.libphonenumber:libphonenumber", version.ref = "libphonenumberJvm" }
+libphonenumber-android = { module = "io.michaelrocks:libphonenumber-android", version.ref = "libphonenumberAndroid" }
[plugins]
cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
diff --git a/library/build.gradle.kts b/library/build.gradle.kts
index 63c36fb..4d0adcc 100644
--- a/library/build.gradle.kts
+++ b/library/build.gradle.kts
@@ -18,6 +18,10 @@ plugins {
}
kotlin {
+ @OptIn(ExperimentalKotlinGradlePluginApi::class)
+ compilerOptions {
+ freeCompilerArgs.add("-Xexpect-actual-classes")
+ }
// cocoapods {
// version = "1.0.0"
// summary = "Yet Another Kotlin COmpose Validation library"
@@ -106,7 +110,8 @@ kotlin {
androidMain.dependencies {
compileOnly(compose.uiTooling)
implementation(libs.androidx.activityCompose)
- api(libs.libphonenumber.android)
+ implementation(libs.libphonenumber.android)
+ implementation(libs.androidx.startup.runtime)
}
jvmMain.dependencies {
@@ -154,6 +159,9 @@ android {
systemImageSource = "aosp"
}
}
+ unitTests {
+ isIncludeAndroidResources = true
+ }
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
diff --git a/library/src/androidInstrumentedTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.android.kt b/library/src/androidInstrumentedTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.android.kt
new file mode 100644
index 0000000..de864f0
--- /dev/null
+++ b/library/src/androidInstrumentedTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.android.kt
@@ -0,0 +1,3 @@
+package com.chrisjenx.yakcov
+
+actual fun initPhoneNumberUtil() = Unit
\ No newline at end of file
diff --git a/library/src/androidMain/AndroidManifest.xml b/library/src/androidMain/AndroidManifest.xml
index fa57d87..95869d5 100644
--- a/library/src/androidMain/AndroidManifest.xml
+++ b/library/src/androidMain/AndroidManifest.xml
@@ -1,2 +1,17 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
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 6192b35..066cbf6 100644
--- a/library/src/androidMain/kotlin/com/chrisjenx/yakcov/Regex.android.kt
+++ b/library/src/androidMain/kotlin/com/chrisjenx/yakcov/Regex.android.kt
@@ -1,10 +1,15 @@
package com.chrisjenx.yakcov
+import android.content.Context
+import androidx.startup.Initializer
import io.michaelrocks.libphonenumber.android.PhoneNumberUtil
-import io.michaelrocks.libphonenumber.android.metadata.init.ClassPathResourceMetadataLoader
+import io.michaelrocks.libphonenumber.android.metadata.source.AssetsMetadataLoader
+import org.jetbrains.annotations.VisibleForTesting
-private val phoneUtil = PhoneNumberUtil.createInstance(ClassPathResourceMetadataLoader())
+@VisibleForTesting
+internal lateinit var phoneUtil: PhoneNumberUtil
+
/**
* Check if is phone number to best ability of each platform.
@@ -19,3 +24,20 @@ actual fun String?.isPhoneNumber(defaultRegion: String?): Boolean {
false
}
}
+
+/**
+ * Will init the Android phone number util at app startup using androidx.startup, see manifest.
+ */
+class PhoneNumberUtilInitializer : Initializer {
+ override fun create(context: Context): PhoneNumberUtil {
+ val assetManager = AssetsMetadataLoader(context.applicationContext.assets)
+ val instance = PhoneNumberUtil.createInstance(assetManager)
+ phoneUtil = instance // Set global
+ return instance
+ }
+
+ override fun dependencies(): List>> {
+ return emptyList()
+ }
+}
+
diff --git a/library/src/androidUnitTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.android.kt b/library/src/androidUnitTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.android.kt
new file mode 100644
index 0000000..3491d7d
--- /dev/null
+++ b/library/src/androidUnitTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.android.kt
@@ -0,0 +1,4 @@
+package com.chrisjenx.yakcov
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+actual annotation class IOSIgnore
\ No newline at end of file
diff --git a/library/src/androidUnitTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.android.kt b/library/src/androidUnitTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.android.kt
new file mode 100644
index 0000000..52dc48c
--- /dev/null
+++ b/library/src/androidUnitTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.android.kt
@@ -0,0 +1,8 @@
+package com.chrisjenx.yakcov
+
+import io.michaelrocks.libphonenumber.android.PhoneNumberUtil
+import io.michaelrocks.libphonenumber.android.metadata.init.ClassPathResourceMetadataLoader
+
+actual fun initPhoneNumberUtil() {
+ phoneUtil = PhoneNumberUtil.createInstance(ClassPathResourceMetadataLoader())
+}
diff --git a/library/src/androidUnitTest/resources/io/michaelrocks/libphonenumber/android/data/PhoneNumberMetadataProto_GB b/library/src/androidUnitTest/resources/io/michaelrocks/libphonenumber/android/data/PhoneNumberMetadataProto_GB
new file mode 100644
index 0000000..77dfe79
Binary files /dev/null and b/library/src/androidUnitTest/resources/io/michaelrocks/libphonenumber/android/data/PhoneNumberMetadataProto_GB differ
diff --git a/library/src/commonMain/kotlin/com/chrisjenx/yakcov/RegexExt.kt b/library/src/commonMain/kotlin/com/chrisjenx/yakcov/RegexExt.kt
index 92117b1..00e6b0e 100644
--- a/library/src/commonMain/kotlin/com/chrisjenx/yakcov/RegexExt.kt
+++ b/library/src/commonMain/kotlin/com/chrisjenx/yakcov/RegexExt.kt
@@ -21,4 +21,4 @@ fun String?.isEmail(): Boolean {
* @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(defaultRegion: String? = null): Boolean
+expect fun String?.isPhoneNumber(defaultRegion: String? = "US"): Boolean
diff --git a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.kt b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.kt
new file mode 100644
index 0000000..996a854
--- /dev/null
+++ b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.kt
@@ -0,0 +1,4 @@
+package com.chrisjenx.yakcov
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+expect annotation class IOSIgnore()
diff --git a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.kt b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.kt
index 027df76..88fa646 100644
--- a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.kt
+++ b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.kt
@@ -1,11 +1,17 @@
package com.chrisjenx.yakcov
+import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class PhoneNumberTest {
+ @BeforeTest
+ fun setUp() {
+ initPhoneNumberUtil()
+ }
+
@Test
fun isPhoneNumber_fail() {
assertFalse("43435".isPhoneNumber())
@@ -21,5 +27,6 @@ class PhoneNumberTest {
assertTrue("6508991234".isPhoneNumber())
}
-
}
+
+expect fun initPhoneNumberUtil()
diff --git a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt
index a350c00..9ed5a38 100644
--- a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt
+++ b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt
@@ -1,11 +1,19 @@
package com.chrisjenx.yakcov.strings
+import com.chrisjenx.yakcov.IOSIgnore
import com.chrisjenx.yakcov.ValidationResult.Outcome
+import com.chrisjenx.yakcov.initPhoneNumberUtil
+import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
class PhoneRuleTest {
+ @BeforeTest
+ fun setUp() {
+ initPhoneNumberUtil()
+ }
+
@Test
fun phoneNumber_invalid() {
assertEquals(Outcome.ERROR, Phone().validate("43435").outcome())
@@ -17,6 +25,7 @@ class PhoneRuleTest {
}
@Test
+ @IOSIgnore
fun phoneNumber_wrongRegion() {
// This is a UK number should error for US
assertEquals(Outcome.ERROR, Phone("US").validate("07745973912").outcome())
@@ -27,5 +36,6 @@ class PhoneRuleTest {
fun phoneNumber_withRegion() {
assertEquals(Outcome.SUCCESS, Phone().validate("+16508991234").outcome())
}
+
}
diff --git a/library/src/iosTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.ios.kt b/library/src/iosTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.ios.kt
new file mode 100644
index 0000000..bc6d4ad
--- /dev/null
+++ b/library/src/iosTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.ios.kt
@@ -0,0 +1,3 @@
+package com.chrisjenx.yakcov
+
+actual typealias IOSIgnore = kotlin.test.Ignore
\ No newline at end of file
diff --git a/library/src/iosTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.ios.kt b/library/src/iosTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.ios.kt
new file mode 100644
index 0000000..de864f0
--- /dev/null
+++ b/library/src/iosTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.ios.kt
@@ -0,0 +1,3 @@
+package com.chrisjenx.yakcov
+
+actual fun initPhoneNumberUtil() = Unit
\ No newline at end of file
diff --git a/library/src/jsTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.js.kt b/library/src/jsTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.js.kt
new file mode 100644
index 0000000..3491d7d
--- /dev/null
+++ b/library/src/jsTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.js.kt
@@ -0,0 +1,4 @@
+package com.chrisjenx.yakcov
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+actual annotation class IOSIgnore
\ No newline at end of file
diff --git a/library/src/jsTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.js.kt b/library/src/jsTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.js.kt
new file mode 100644
index 0000000..de864f0
--- /dev/null
+++ b/library/src/jsTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.js.kt
@@ -0,0 +1,3 @@
+package com.chrisjenx.yakcov
+
+actual fun initPhoneNumberUtil() = Unit
\ No newline at end of file
diff --git a/library/src/jvmTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.jvm.kt b/library/src/jvmTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.jvm.kt
new file mode 100644
index 0000000..3491d7d
--- /dev/null
+++ b/library/src/jvmTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.jvm.kt
@@ -0,0 +1,4 @@
+package com.chrisjenx.yakcov
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+actual annotation class IOSIgnore
\ No newline at end of file
diff --git a/library/src/jvmTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.jvm.kt b/library/src/jvmTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.jvm.kt
new file mode 100644
index 0000000..de864f0
--- /dev/null
+++ b/library/src/jvmTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.jvm.kt
@@ -0,0 +1,3 @@
+package com.chrisjenx.yakcov
+
+actual fun initPhoneNumberUtil() = Unit
\ No newline at end of file
diff --git a/library/src/wasmJsTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.wasmJs.kt b/library/src/wasmJsTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.wasmJs.kt
new file mode 100644
index 0000000..3491d7d
--- /dev/null
+++ b/library/src/wasmJsTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.wasmJs.kt
@@ -0,0 +1,4 @@
+package com.chrisjenx.yakcov
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+actual annotation class IOSIgnore
\ No newline at end of file
diff --git a/library/src/wasmJsTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.wasmJs.kt b/library/src/wasmJsTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.wasmJs.kt
new file mode 100644
index 0000000..de864f0
--- /dev/null
+++ b/library/src/wasmJsTest/kotlin/com/chrisjenx/yakcov/PhoneNumberTest.wasmJs.kt
@@ -0,0 +1,3 @@
+package com.chrisjenx.yakcov
+
+actual fun initPhoneNumberUtil() = Unit
\ No newline at end of file
diff --git a/sample/src/main/java/com/chrisjenx/yakcov/sample/SampleActivity.kt b/sample/src/main/java/com/chrisjenx/yakcov/sample/SampleActivity.kt
index 235cc9b..2f369e1 100644
--- a/sample/src/main/java/com/chrisjenx/yakcov/sample/SampleActivity.kt
+++ b/sample/src/main/java/com/chrisjenx/yakcov/sample/SampleActivity.kt
@@ -13,8 +13,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
@@ -27,14 +29,12 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.chrisjenx.yakcov.generic.IsChecked
import com.chrisjenx.yakcov.generic.ListNotEmpty
@@ -43,6 +43,7 @@ import com.chrisjenx.yakcov.sample.ui.theme.YakcovTheme
import com.chrisjenx.yakcov.strings.Email
import com.chrisjenx.yakcov.strings.MinLength
import com.chrisjenx.yakcov.strings.PasswordMatches
+import com.chrisjenx.yakcov.strings.Phone
import com.chrisjenx.yakcov.strings.Required
import com.chrisjenx.yakcov.strings.rememberTextFieldValueValidator
import com.chrisjenx.yakcov.validate
@@ -57,6 +58,7 @@ class SampleActivity : ComponentActivity() {
Column(
modifier = Modifier
.fillMaxSize()
+ .verticalScroll(rememberScrollState())
.padding(innerPadding)
.padding(16.dp)
) {
@@ -87,6 +89,29 @@ class SampleActivity : ComponentActivity() {
Spacer(modifier = Modifier.height(16.dp))
+ val phoneValidator = rememberTextFieldValueValidator(
+ rules = listOf(Required, Phone()),
+ )
+ with(phoneValidator) {
+ OutlinedTextField(
+ value = value,
+ label = { Text(text = "Phone") },
+ modifier = Modifier
+ .validationConfig(validateOnFocusLost = true)
+ .fillMaxWidth(),
+ onValueChange = ::onValueChange,
+ isError = isError(),
+ keyboardOptions = KeyboardOptions(
+ autoCorrectEnabled = false,
+ keyboardType = KeyboardType.Phone,
+ ),
+ singleLine = true,
+ supportingText = supportingText()
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
Text(text = "Password", style = MaterialTheme.typography.headlineSmall)
// Password example
val passwordValidator = rememberTextFieldValueValidator(
@@ -255,6 +280,7 @@ class SampleActivity : ComponentActivity() {
onClick = {
listOf(
emailValidator,
+ phoneValidator,
passwordValidator,
passwordMatchesValidator,
requiredValidator,
@@ -274,19 +300,3 @@ class SampleActivity : ComponentActivity() {
}
}
}
-
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name!",
- modifier = modifier
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
- YakcovTheme {
- Greeting("Android")
- }
-}
\ No newline at end of file