2023-01-18 13:36:42 +00:00
|
|
|
package com.vitorpamplona.amethyst.ui.actions
|
|
|
|
|
|
|
|
import android.util.Patterns
|
|
|
|
import androidx.compose.ui.graphics.Color
|
|
|
|
import androidx.compose.ui.text.AnnotatedString
|
|
|
|
import androidx.compose.ui.text.SpanStyle
|
|
|
|
import androidx.compose.ui.text.TextRange
|
|
|
|
import androidx.compose.ui.text.buildAnnotatedString
|
|
|
|
import androidx.compose.ui.text.input.OffsetMapping
|
|
|
|
import androidx.compose.ui.text.input.TransformedText
|
|
|
|
import androidx.compose.ui.text.input.VisualTransformation
|
|
|
|
import androidx.compose.ui.text.style.TextDecoration
|
|
|
|
import com.vitorpamplona.amethyst.model.LocalCache
|
2023-08-16 21:58:25 +00:00
|
|
|
import com.vitorpamplona.quartz.encoders.decodePublicKey
|
|
|
|
import com.vitorpamplona.quartz.encoders.toHexKey
|
2023-01-18 13:36:42 +00:00
|
|
|
import kotlin.math.roundToInt
|
|
|
|
|
|
|
|
data class RangesChanges(val original: TextRange, val modified: TextRange)
|
|
|
|
|
|
|
|
class UrlUserTagTransformation(val color: Color) : VisualTransformation {
|
2023-03-07 18:46:44 +00:00
|
|
|
override fun filter(text: AnnotatedString): TransformedText {
|
|
|
|
return buildAnnotatedStringWithUrlHighlighting(text, color)
|
|
|
|
}
|
2023-01-18 13:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun buildAnnotatedStringWithUrlHighlighting(text: AnnotatedString, color: Color): TransformedText {
|
|
|
|
val substitutions = mutableListOf<RangesChanges>()
|
|
|
|
|
|
|
|
val newText = buildAnnotatedString {
|
2023-03-07 18:46:44 +00:00
|
|
|
val builderBefore = StringBuilder() // important to correctly measure Tag start and end
|
|
|
|
val builderAfter = StringBuilder() // important to correctly measure Tag start and end
|
|
|
|
append(
|
|
|
|
text.split('\n').map { paragraph: String ->
|
|
|
|
paragraph.split(' ').map { word: String ->
|
|
|
|
try {
|
|
|
|
if (word.startsWith("@npub") && word.length >= 64) {
|
|
|
|
val keyB32 = word.substring(0, 64)
|
|
|
|
val restOfWord = word.substring(64)
|
|
|
|
|
|
|
|
val startIndex = builderBefore.toString().length
|
|
|
|
|
|
|
|
builderBefore.append("$keyB32$restOfWord ") // accounts for the \n at the end of each paragraph
|
|
|
|
|
|
|
|
val endIndex = startIndex + keyB32.length
|
|
|
|
|
2023-08-16 21:58:25 +00:00
|
|
|
val key =
|
|
|
|
decodePublicKey(keyB32.removePrefix("@"))
|
2023-03-07 18:46:44 +00:00
|
|
|
val user = LocalCache.getOrCreateUser(key.toHexKey())
|
|
|
|
|
|
|
|
val newWord = "@${user.toBestDisplayName()}"
|
|
|
|
val startNew = builderAfter.toString().length
|
|
|
|
|
|
|
|
builderAfter.append("$newWord$restOfWord ") // accounts for the \n at the end of each paragraph
|
|
|
|
|
|
|
|
substitutions.add(
|
|
|
|
RangesChanges(
|
|
|
|
TextRange(startIndex, endIndex),
|
|
|
|
TextRange(startNew, startNew + newWord.length)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
newWord + restOfWord
|
|
|
|
} else {
|
|
|
|
builderBefore.append(word + " ")
|
|
|
|
builderAfter.append(word + " ")
|
|
|
|
word
|
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
// if it can't parse the key, don't try to change.
|
|
|
|
builderBefore.append(word + " ")
|
|
|
|
builderAfter.append(word + " ")
|
|
|
|
word
|
|
|
|
}
|
|
|
|
}.joinToString(" ")
|
|
|
|
}.joinToString("\n")
|
2023-01-18 13:36:42 +00:00
|
|
|
)
|
2023-03-07 18:46:44 +00:00
|
|
|
|
|
|
|
val newText = toAnnotatedString()
|
|
|
|
|
|
|
|
newText.split("\\s+".toRegex()).filter { word ->
|
|
|
|
Patterns.WEB_URL.matcher(word).matches()
|
|
|
|
}.forEach {
|
|
|
|
val startIndex = text.indexOf(it)
|
|
|
|
val endIndex = startIndex + it.length
|
|
|
|
addStyle(
|
|
|
|
style = SpanStyle(
|
|
|
|
color = color,
|
|
|
|
textDecoration = TextDecoration.None
|
|
|
|
),
|
|
|
|
start = startIndex,
|
|
|
|
end = endIndex
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
substitutions.forEach {
|
|
|
|
addStyle(
|
|
|
|
style = SpanStyle(
|
|
|
|
color = color,
|
|
|
|
textDecoration = TextDecoration.None
|
|
|
|
),
|
|
|
|
start = it.modified.start,
|
|
|
|
end = it.modified.end
|
|
|
|
)
|
|
|
|
}
|
2023-01-18 13:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
val numberOffsetTranslator = object : OffsetMapping {
|
|
|
|
override fun originalToTransformed(offset: Int): Int {
|
2023-03-07 18:46:44 +00:00
|
|
|
val inInsideRange = substitutions.filter { offset > it.original.start && offset < it.original.end }.firstOrNull()
|
2023-01-18 13:36:42 +00:00
|
|
|
|
2023-03-07 18:46:44 +00:00
|
|
|
if (inInsideRange != null) {
|
|
|
|
val percentInRange = (offset - inInsideRange.original.start) / (inInsideRange.original.length.toFloat())
|
|
|
|
return (inInsideRange.modified.start + inInsideRange.modified.length * percentInRange).roundToInt()
|
|
|
|
}
|
2023-01-18 13:36:42 +00:00
|
|
|
|
2023-03-07 18:46:44 +00:00
|
|
|
val lastRangeThrough = substitutions.lastOrNull { offset >= it.original.end }
|
2023-01-18 13:36:42 +00:00
|
|
|
|
2023-03-07 18:46:44 +00:00
|
|
|
if (lastRangeThrough != null) {
|
|
|
|
return lastRangeThrough.modified.end + (offset - lastRangeThrough.original.end)
|
|
|
|
} else {
|
|
|
|
return offset
|
|
|
|
}
|
2023-01-18 13:36:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun transformedToOriginal(offset: Int): Int {
|
2023-03-07 18:46:44 +00:00
|
|
|
val inInsideRange = substitutions.filter { offset > it.modified.start && offset < it.modified.end }.firstOrNull()
|
2023-01-18 13:36:42 +00:00
|
|
|
|
2023-03-07 18:46:44 +00:00
|
|
|
if (inInsideRange != null) {
|
|
|
|
val percentInRange = (offset - inInsideRange.modified.start) / (inInsideRange.modified.length.toFloat())
|
|
|
|
return (inInsideRange.original.start + inInsideRange.original.length * percentInRange).roundToInt()
|
|
|
|
}
|
2023-01-18 13:36:42 +00:00
|
|
|
|
2023-03-07 18:46:44 +00:00
|
|
|
val lastRangeThrough = substitutions.lastOrNull { offset >= it.modified.end }
|
2023-01-18 13:36:42 +00:00
|
|
|
|
2023-03-07 18:46:44 +00:00
|
|
|
if (lastRangeThrough != null) {
|
|
|
|
return lastRangeThrough.original.end + (offset - lastRangeThrough.modified.end)
|
|
|
|
} else {
|
|
|
|
return offset
|
|
|
|
}
|
2023-01-18 13:36:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return TransformedText(
|
2023-03-07 18:46:44 +00:00
|
|
|
newText,
|
|
|
|
numberOffsetTranslator
|
2023-01-18 13:36:42 +00:00
|
|
|
)
|
2023-03-07 18:46:44 +00:00
|
|
|
}
|