Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ private inline fun getPrivacyPolicyAnnotatedString(
privacyPolicy: String,
crossinline onPrivacyPolicyClick: () -> Unit,
) = intervalAnnotatedString(privacyPolicy)
.asAnnotatedString { id, startsAt, length ->
.asAnnotatedString { id, startsAt, endsAt ->
if (id == PRIVACY_POLICY_LINK_INTERVAL_ID) {
addLink(
clickable =
Expand All @@ -154,7 +154,7 @@ private inline fun getPrivacyPolicyAnnotatedString(
linkInteractionListener = { onPrivacyPolicyClick() },
),
start = startsAt,
end = startsAt + length,
end = endsAt,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ fun CareersPromotionCard(
)
Text(
text = intervalAnnotatedString(R.string.careers_promotion_card_text)
.asAnnotatedString { id, startsAt, length ->
val endsAt = startsAt + length

.asAnnotatedString { id, startsAt, endsAt ->
if (id == URL_ANNOTATION_ID) {
addLink(
clickable = LinkAnnotation.Clickable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,7 @@ private fun buildAnnotatedStringPreview(

return try {
val annotatedString = IntervalAnnotatedString(text)
.asAnnotatedString { id, startsAt, length ->
val endsAt = startsAt + length

.asAnnotatedString { id, startsAt, endsAt ->
if (!coloursLookup.containsKey(id)) {
coloursLookup[id] = Color(
Random.nextInt(256),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,7 @@ private fun SampleContentItemRenderingStringViewer(
) {
Text(
text = intervalAnnotatedString(rawText)
.asAnnotatedString { id, startsAt, length ->
val endsAt = startsAt + length

.asAnnotatedString { id, startsAt, endsAt ->
val annotation = annotationsLookup[id]
when (annotation) {
is SpanStyle ->
Expand Down
1 change: 1 addition & 0 deletions intervalannotatedstring/api/intervalannotatedstring.api
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public final class com/justeattakeaway/intervalannotatedstring/IntervalAnnotated

public final class com/justeattakeaway/intervalannotatedstring/IntervalAnnotatedStringExtsKt {
public static final fun asAnnotatedString-QpJNx2M (Ljava/lang/String;Lkotlin/jvm/functions/Function4;)Landroidx/compose/ui/text/AnnotatedString;
public static final fun asSpannableString-QpJNx2M (Ljava/lang/String;Lkotlin/jvm/functions/Function4;)Landroid/text/SpannableString;
}

public final class com/justeattakeaway/intervalannotatedstring/IntervalAnnotatedStringFactoryKt {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import com.justeattakeaway.intervalannotatedstring.InlineIntervalSyntaxParser.No
typealias OnTransformText<T> = (text: String) -> T

/**
* Applies interval transformation on the target rendering string implementation.
* Applies interval transformation on the target rendering string implementation at
* inclusive startsAt and exclusive endsAt bounds.
*/
typealias OnTransformInterval<T> = T.(id: String, startsAt: Int, length: Int) -> Unit
typealias OnApplyIntervalTransformation<T> = T.(id: String, startsAt: Int, endsAt: Int) -> Unit
Comment on lines -16 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure why this change doesn't appear in the API diff? Why isn't this a breaking change?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was also thinking about it. My guess because this is typealias and in the public API it is substituted with Lkotlin/jvm/functions/Function4;. So this name does not really exist in bytecode.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you able to do some checks as to whether this is breaking or not? It's cool if it's not a breaking change. I just haven't seen this before. So don't know.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check runs automatically on CI. I was able to run it locally and it is passing: so I believe this is not a breaking change. I agree this is weird as this is a public name though I believe the name is inlined that's why it is not picked up in the resulting API file.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to be a pain but whilst this might not break binary compatibility it does break source compatibility.

Given you've listed this as alpha, and a very early alpha as well, I think that helps us here.

Screenshot_20250828-203318.png

What I think we should do is for now simply call out the change in a change log before publishing a new version. Ping me tomorrow I can help with that.

I think we should also decide together with @jordicollmarin how we want to evolve the API here in this project. I'm keen that we preserve binary and source compatibility but I understand that adds complexity. Again, this library still being an alpha release buys us time 👍.

When the API stabilises to the point where we release a 1.0.0 then we really need to be careful.... even about renaming parameters.

https://kotlinlang.org/docs/api-guidelines-backward-compatibility.html

But I think we're ok for now. Great job 🤗

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mezpahlan no worries, that's definitely a valid point. Let's catch up tomorrow on the evolution of the API.

Given you've listed this as alpha, and a very early alpha as well, I think that helps us here.

Yeah, we have no obligation for now to keep the API intact, especially, while we are still shaping it.

What I think we should do is for now simply call out the change in a change log before publishing a new version.

It would be really cool if we can generate a changelog automatically.

Let's think about possible changes tomorrow as I have one potential API change in mind that I think we may want to implement.


/**
* Primary class for working with interval annotated strings.
Expand All @@ -26,14 +27,14 @@ value class IntervalAnnotatedString(
/**
* Performs a transformation of raw text (with embedded intervals metadata) to the target rendering string
* implementation. Can be used to transform the string to an instance of
* [androidx.compose.ui.text.AnnotatedString] or [com.jet.pie2.utils.StyledString].
* [androidx.compose.ui.text.AnnotatedString] or [android.text.SpannableString].
*
* @param T The target type for the transformed string. This is typically a class
* designed to represent a rendering text, such as
* [androidx.compose.ui.text.AnnotatedString] or [com.jet.pie2.utils.StyledString].
* [androidx.compose.ui.text.AnnotatedString] or [android.text.SpannableString].
* The chosen type [T] should generally be constructible from a plain string (via [onTransformText]) and
* should allow for range-based modifications, styling, or annotations
* (via [onTransformInterval]) to be useful with this transformation.
* (via [onApplyIntervalTransformation]) to be useful with this transformation.
*
* @throws NoIdException if the id subsection is empty
* @throws EmptyInlineTextException if the raw text within the interval annotated string area is empty
Expand All @@ -43,12 +44,16 @@ value class IntervalAnnotatedString(
@Throws(NoIdException::class, EmptyInlineTextException::class)
inline fun <T> transform(
onTransformText: OnTransformText<T>,
onTransformInterval: OnTransformInterval<T>,
onApplyIntervalTransformation: OnApplyIntervalTransformation<T>,
): T {
val (clearedText, intervals) = InlineIntervalSyntaxParser.parseMetadata(rawString)
val outResult = onTransformText(clearedText)
for (interval in intervals) {
outResult.onTransformInterval(interval.id, interval.startsAt, interval.length)
outResult.onApplyIntervalTransformation(
interval.id,
interval.startsAt,
interval.startsAt + interval.length, // endsAt
)
}
return outResult
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
package com.justeattakeaway.intervalannotatedstring

import android.text.SpannableString
import androidx.annotation.AnyThread
import androidx.annotation.CheckResult
import androidx.compose.ui.text.AnnotatedString
import com.justeattakeaway.intervalannotatedstring.InlineIntervalSyntaxParser.EmptyInlineTextException
import com.justeattakeaway.intervalannotatedstring.InlineIntervalSyntaxParser.NoIdException

/**
* Converts an interval annotated string instance to AnnotatedString.
* Converts an interval annotated string instance to [SpannableString].
*
* @see IntervalAnnotatedString
* @see SpannableString
*/
@AnyThread
@CheckResult
@Throws(NoIdException::class, EmptyInlineTextException::class)
inline fun IntervalAnnotatedString.asSpannableString(
onAddIntervalStyle: OnApplyIntervalTransformation<SpannableString>
): SpannableString =
transform(
onTransformText = { SpannableString(it) },
onApplyIntervalTransformation = onAddIntervalStyle,
)

/**
* Converts an interval annotated string instance to [AnnotatedString].
*
* @see IntervalAnnotatedString
* @see AnnotatedString
Expand All @@ -16,9 +34,9 @@ import com.justeattakeaway.intervalannotatedstring.InlineIntervalSyntaxParser.No
@CheckResult
@Throws(NoIdException::class, EmptyInlineTextException::class)
inline fun IntervalAnnotatedString.asAnnotatedString(
onAddIntervalStyle: OnTransformInterval<AnnotatedString.Builder>
onAddIntervalStyle: OnApplyIntervalTransformation<AnnotatedString.Builder>
): AnnotatedString =
transform(
onTransformText = { AnnotatedString.Builder(it) },
onTransformInterval = onAddIntervalStyle,
onApplyIntervalTransformation = onAddIntervalStyle,
).toAnnotatedString()
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ typealias TestRenderingString = String
@RunWith(JUnit4::class)
class IntervalAnnotatedStringTest {
private val onTransformText = mockk<OnTransformText<TestRenderingString>>()
private val onTransformInterval = mockk<OnTransformInterval<TestRenderingString>>()
private val onApplyIntervalTransformation = mockk<OnApplyIntervalTransformation<TestRenderingString>>()

@Before
fun setUp() {
mockkObject(InlineIntervalSyntaxParser)

every { onTransformText.invoke(any()) } returns TEST_RENDERING_STRING
every { onTransformInterval.invoke(any(), any(), any(), any()) } returns Unit
every { onApplyIntervalTransformation.invoke(any(), any(), any(), any()) } returns Unit
}

@SuppressLint("CheckResult")
Expand All @@ -39,7 +39,7 @@ class IntervalAnnotatedStringTest {
)

// When
text.transform(onTransformText, onTransformInterval)
text.transform(onTransformText, onApplyIntervalTransformation)

// Then
verify(exactly = 1) { onTransformText.invoke(eq(TEST_INTERVAL_CLEARED_STRING)) }
Expand All @@ -57,27 +57,27 @@ class IntervalAnnotatedStringTest {
)

// When
text.transform(onTransformText, onTransformInterval)
text.transform(onTransformText, onApplyIntervalTransformation)

// Then
verify {
onTransformInterval.invoke(
onApplyIntervalTransformation.invoke(
eq(TEST_RENDERING_STRING),
eq(TEST_INTERVAL_1.id),
eq(TEST_INTERVAL_1.startsAt),
eq(TEST_INTERVAL_1.length),
eq(TEST_INTERVAL_1.startsAt + TEST_INTERVAL_1.length),
)
onTransformInterval.invoke(
onApplyIntervalTransformation.invoke(
eq(TEST_RENDERING_STRING),
eq(TEST_INTERVAL_2.id),
eq(TEST_INTERVAL_2.startsAt),
eq(TEST_INTERVAL_2.length),
eq(TEST_INTERVAL_2.startsAt + TEST_INTERVAL_2.length),
)
onTransformInterval.invoke(
onApplyIntervalTransformation.invoke(
eq(TEST_RENDERING_STRING),
eq(TEST_INTERVAL_3.id),
eq(TEST_INTERVAL_3.startsAt),
eq(TEST_INTERVAL_3.length),
eq(TEST_INTERVAL_3.startsAt + TEST_INTERVAL_3.length),
)
}
}
Expand Down