kopia lustrzana https://github.com/vitorpamplona/amethyst
123 wiersze
4.3 KiB
Kotlin
123 wiersze
4.3 KiB
Kotlin
package com.vitorpamplona.amethyst.ui.components
|
|
|
|
import android.util.Patterns
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.foundation.text.ClickableText
|
|
import androidx.compose.material.LocalTextStyle
|
|
import androidx.compose.material.MaterialTheme
|
|
import androidx.compose.material.Text
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.livedata.observeAsState
|
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.text.AnnotatedString
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.navigation.NavController
|
|
import com.google.accompanist.flowlayout.FlowRow
|
|
import com.vitorpamplona.amethyst.model.LocalCache
|
|
import com.vitorpamplona.amethyst.model.Note
|
|
import com.vitorpamplona.amethyst.model.toNote
|
|
import com.vitorpamplona.amethyst.ui.note.toDisplayHex
|
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|
import java.net.MalformedURLException
|
|
import java.net.URISyntaxException
|
|
import java.net.URL
|
|
import java.util.regex.Pattern
|
|
|
|
val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp)$")
|
|
val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm)$")
|
|
val noProtocolUrlValidator = Pattern.compile("^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$")
|
|
val tagIndex = Pattern.compile(".*\\#\\[([0-9]+)\\].*")
|
|
|
|
val mentionsPattern: Pattern = Pattern.compile("@([A-Za-z0-9_-]+)")
|
|
val hashTagsPattern: Pattern = Pattern.compile("#([A-Za-z0-9_-]+)")
|
|
val urlPattern: Pattern = Patterns.WEB_URL
|
|
|
|
fun isValidURL(url: String?): Boolean {
|
|
return try {
|
|
URL(url).toURI()
|
|
true
|
|
} catch (e: MalformedURLException) {
|
|
false
|
|
} catch (e: URISyntaxException) {
|
|
false
|
|
}
|
|
}
|
|
|
|
@OptIn(ExperimentalComposeUiApi::class)
|
|
@Composable
|
|
fun RichTextViewer(content: String, tags: List<List<String>>?, note: Note, accountViewModel: AccountViewModel, navController: NavController) {
|
|
Column(modifier = Modifier.padding(top = 5.dp)) {
|
|
// FlowRow doesn't work well with paragraphs. So we need to split them
|
|
content.split('\n').forEach { paragraph ->
|
|
|
|
FlowRow() {
|
|
paragraph.split(' ').forEach { word: String ->
|
|
// Explicit URL
|
|
val lnInvoice = LnInvoiceUtil.findInvoice(word)
|
|
if (lnInvoice != null) {
|
|
InvoicePreview(lnInvoice)
|
|
} else if (isValidURL(word)) {
|
|
val removedParamsFromUrl = word.split("?")[0].toLowerCase()
|
|
if (imageExtension.matcher(removedParamsFromUrl).matches()) {
|
|
ExtendedImageView(word)
|
|
} else if (videoExtension.matcher(removedParamsFromUrl).matches()) {
|
|
VideoView(word)
|
|
} else {
|
|
UrlPreview(word, word)
|
|
}
|
|
} else if (noProtocolUrlValidator.matcher(word).matches()) {
|
|
UrlPreview("https://$word", word)
|
|
} else if (tagIndex.matcher(word).matches() && tags != null) {
|
|
TagLink(word, tags, navController)
|
|
} else {
|
|
Text(text = "$word ")
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
fun TagLink(word: String, tags: List<List<String>>, navController: NavController) {
|
|
val matcher = tagIndex.matcher(word)
|
|
|
|
val index = try {
|
|
matcher.find()
|
|
matcher.group(1).toInt()
|
|
} catch (e: Exception) {
|
|
println("Couldn't link tag ${word}")
|
|
null
|
|
}
|
|
|
|
if (index == null) {
|
|
return Text(text = "$word ")
|
|
}
|
|
|
|
if (index > 0 && index < tags.size) {
|
|
if (tags[index][0] == "p") {
|
|
val user = LocalCache.users[tags[index][1]]
|
|
if (user != null) {
|
|
val innerUserState by user.live.observeAsState()
|
|
Text(
|
|
"@${innerUserState?.user?.toBestDisplayName()} "
|
|
)
|
|
}
|
|
} else if (tags[index][0] == "e") {
|
|
val note = LocalCache.notes[tags[index][1]]
|
|
if (note != null) {
|
|
val innerNoteState by note.live.observeAsState()
|
|
ClickableText(
|
|
text = AnnotatedString("@${innerNoteState?.note?.id?.toNote()?.toDisplayHex()} "),
|
|
onClick = { navController.navigate("Note/${note.idHex}") },
|
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
|
)
|
|
}
|
|
} else
|
|
Text(text = "$word ")
|
|
}
|
|
} |