Skip to content

Commit

Permalink
add default annotationProviders and test
Browse files Browse the repository at this point in the history
  • Loading branch information
Calvin-LL committed Mar 8, 2024
1 parent acb8d38 commit 6a26509
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package sh.calvin.autolinktext

import android.telephony.PhoneNumberUtils
import androidx.core.net.toUri
import java.net.URL

internal actual fun getMatchAnnotationProviderDefaults() =
object : MatchAnnotationProviderDefaultsInterface {
override val WebUrl: MatchAnnotationProvider<*>
get() = {
val url = it.matchedText.toUri().let {
if (it.scheme == null)
URL("https://${it}")
else
it
}
url.toString()
}

override val EmailAddress: MatchAnnotationProvider<*>
get() = {
"mailto:${it.matchedText}"
}

override val PhoneNumber: MatchAnnotationProvider<*>
get() = {
val phone = PhoneNumberUtils.normalizeNumber(it.matchedText)
"tel:${phone}"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package sh.calvin.autolinktext

import androidx.compose.ui.text.ExperimentalTextApi
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
Expand Down Expand Up @@ -104,4 +105,36 @@ class AndroidTextRuleTest {
assertEquals("604-555-7890", text.slice(matches[5]))
assertEquals(rules[2], matches[5].rule)
}

@OptIn(NotForAndroid::class, ExperimentalTextApi::class)
@Test
fun testAnnotateString() {
val context = RuntimeEnvironment.getApplication()
val contextData = AndroidContextData(context)

val text = "Visit https://www.google.com\n" +
"Visit www.google.com\n" +
"Email test@example.com\n" +
"Call 6045557890\n" +
"Call +1 (604) 555-7890\n" +
"Call 604-555-7890\n"
val rules = listOf(
TextRuleDefaults.webUrl(contextData),
TextRuleDefaults.emailAddress(contextData),
TextRuleDefaults.phoneNumber(contextData),
)
val matches = rules.getAllMatches(text)
val annotatedString = matches.annotateString(text)

fun getUrlAtMatch(index: Int) = annotatedString.getUrlAnnotations(
matches[index].start, matches[index].endExclusive
).first().item.url

assertEquals("https://www.google.com", getUrlAtMatch(0))
assertEquals("https://www.google.com", getUrlAtMatch(1))
assertEquals("mailto:test@example.com", getUrlAtMatch(2))
assertEquals("tel:6045557890", getUrlAtMatch(3))
assertEquals("tel:+16045557890", getUrlAtMatch(4))
assertEquals("tel:6045557890", getUrlAtMatch(5))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,27 @@ package sh.calvin.autolinktext
*/
typealias MatchAnnotationProvider<T> = (match: TextMatchResult<T>) -> String?

object MatchAnnotationProviderDefaults {
val NoAnnotation: MatchAnnotationProvider<*> = { null }
interface MatchAnnotationProviderDefaultsInterface {
val NoAnnotation: MatchAnnotationProvider<*>
get() = { null }

/**
* A [MatchAnnotationProvider] that uses the matched text as the URL
* This lets screen readers know that the text is clickable, but doesn't provide a different URL
*/
val Verbatim: MatchAnnotationProvider<*> = { it.matchedText }
val Verbatim: MatchAnnotationProvider<*>
get() = { it.matchedText }

val WebUrl: MatchAnnotationProvider<*>
get() = { it.matchedText }

val EmailAddress: MatchAnnotationProvider<*>
get() = { it.matchedText }

val PhoneNumber: MatchAnnotationProvider<*>
get() = { it.matchedText }
}

internal expect fun getMatchAnnotationProviderDefaults(): MatchAnnotationProviderDefaultsInterface

val MatchAnnotationProviderDefaults = getMatchAnnotationProviderDefaults()
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@ interface TextRuleDefaultsInterface {
fun webUrl(contextData: ContextData = NullContextData) = TextRule(
textMatcher = TextMatcherDefaults.webUrl(contextData),
onClick = MatchClickHandlerDefaults.webUrl(contextData),
annotationProvider = MatchAnnotationProviderDefaults.WebUrl,
)

@NotForAndroid
fun emailAddress(contextData: ContextData = NullContextData) = TextRule(
textMatcher = TextMatcherDefaults.emailAddress(contextData),
onClick = MatchClickHandlerDefaults.emailAddress(contextData),
annotationProvider = MatchAnnotationProviderDefaults.EmailAddress,
)

@NotForAndroid
fun phoneNumber(contextData: ContextData = NullContextData) = TextRule(
textMatcher = TextMatcherDefaults.phoneNumber(contextData),
onClick = MatchClickHandlerDefaults.phoneNumber(contextData),
annotationProvider = MatchAnnotationProviderDefaults.PhoneNumber,
)

@NotForAndroid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNull
import androidx.compose.ui.text.ExperimentalTextApi

class TextRuleTest {
@Test
Expand Down Expand Up @@ -200,27 +201,39 @@ class TextRuleTest {
assertEquals(rules[1], matches[2].rule)
assertEquals("6045557890", text.slice(matches[3]))
assertEquals(rules[2], matches[3].rule)
assertEquals("+1 (604) 555-7890", text.slice(matches[4]))
assertEqualsOneOf(listOf("+1 (604) 555-7890", "(604) 555-7890"), text.slice(matches[4]))
assertEquals(rules[2], matches[4].rule)
assertEquals("604-555-7890", text.slice(matches[5]))
assertEquals(rules[2], matches[5].rule)
}

@OptIn(NotForAndroid::class)
@OptIn(NotForAndroid::class, ExperimentalTextApi::class)
@IgnoreAndroid
@Test
fun textWithAnnotations() {
val text = "read our privacy policy"
fun testAnnotateString() {
val text = "Visit https://www.google.com\n" +
"Visit www.google.com\n" +
"Email test@example.com\n" +
"Call 6045557890\n" +
"Call +1 (604) 555-7890\n" +
"Call 604-555-7890\n"
val rules = listOf(
TextRule(
textMatcher = TextMatcher.StringMatcher("privacy policy"),
annotationProvider = { "https://example.com/privacy" },
),
TextRuleDefaults.webUrl(NullContextData),
TextRuleDefaults.emailAddress(NullContextData),
TextRuleDefaults.phoneNumber(NullContextData),
)
val matches = rules.getAllMatches(text)

assertEquals(1, matches.size)
assertEquals("someone@example.com", text.slice(matches[0]))
assertEquals(rules[1], matches[0].rule)
val annotatedString = matches.annotateString(text)

fun getUrlAtMatch(index: Int) = annotatedString.getUrlAnnotations(
matches[index].start, matches[index].endExclusive
).first().item.url

assertEquals("https://www.google.com", getUrlAtMatch(0))
assertEqualsOneOf(listOf("https://www.google.com", "http://www.google.com"), getUrlAtMatch(1))
assertEquals("mailto:test@example.com", getUrlAtMatch(2))
assertEquals("tel:6045557890", getUrlAtMatch(3))
assertEquals("tel:+16045557890", getUrlAtMatch(4))
assertEquals("tel:6045557890", getUrlAtMatch(5))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package sh.calvin.autolinktext

import kotlin.test.assertTrue

fun assertEqualsOneOf(expected: List<String>, actual: String) {
assertTrue(expected.contains(actual), "Expected one of $expected, but was $actual")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package sh.calvin.autolinktext

import platform.Foundation.NSURL
import platform.Foundation.NSURLComponents

object IosMatchAnnotationProviderDefaults : MatchAnnotationProviderDefaultsInterface {
override val WebUrl: MatchAnnotationProvider<*>
get() = {
val url = when (val data = it.data) {
is NSURL -> data
else -> NSURLComponents(it.matchedText).apply {
scheme = scheme ?: "https"
}.URL
}
url?.absoluteString
}

override val EmailAddress: MatchAnnotationProvider<*>
get() = {
val url = when (val data = it.data) {
is NSURL -> data
else -> NSURL(string = "mailto:${it.matchedText}")
}
url.absoluteString
}

override val PhoneNumber: MatchAnnotationProvider<*>
get() = {
val url = when (val data = it.data) {
is NSURL -> data
else -> NSURL(string = "tel:${normalizePhoneNumber(it.matchedText)}")
}
url.absoluteString
}
}

internal actual fun getMatchAnnotationProviderDefaults(): MatchAnnotationProviderDefaultsInterface =
IosMatchAnnotationProviderDefaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package sh.calvin.autolinktext

import java.net.URI

internal actual fun getMatchAnnotationProviderDefaults() =
object : MatchAnnotationProviderDefaultsInterface {
override val WebUrl: MatchAnnotationProvider<*>
get() = { result ->
val url = URI(result.matchedText).let {
if (it.scheme == null || it.scheme.isEmpty()) {
URI("https://${result.matchedText}")
} else {
it
}
}
url.toString()
}

override val EmailAddress: MatchAnnotationProvider<*>
get() = {
"mailto:${it.matchedText}"
}

override val PhoneNumber: MatchAnnotationProvider<*>
get() = {
"tel:${normalizePhoneNumber(it.matchedText)}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package sh.calvin.autolinktext

internal actual fun getMatchAnnotationProviderDefaults() =
object : MatchAnnotationProviderDefaultsInterface {
override val WebUrl: MatchAnnotationProvider<*>
get() = {
var url = it.matchedText
val protocolRegex = Regex("^\\S+://.+$")
val hasProtocol = protocolRegex.matches(url)
if (!hasProtocol) {
url = "https://$url"
}
url
}

override val EmailAddress: MatchAnnotationProvider<*>
get() = {
"mailto:${it.matchedText}"
}

override val PhoneNumber: MatchAnnotationProvider<*>
get() = {
"tel:${normalizePhoneNumber(it.matchedText)}"
}
}

0 comments on commit 6a26509

Please sign in to comment.