amethyst/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt

114 wiersze
3.7 KiB
Kotlin
Czysty Zwykły widok Historia

2023-01-11 18:31:20 +00:00
package com.vitorpamplona.amethyst.ui.components
2023-01-13 17:30:13 +00:00
import android.util.Patterns
2023-01-11 18:31:20 +00:00
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
2023-01-14 01:16:57 +00:00
import androidx.compose.ui.ExperimentalComposeUiApi
2023-01-11 18:31:20 +00:00
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.model.LocalCache
2023-01-14 01:16:57 +00:00
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
2023-01-11 18:31:20 +00:00
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)$")
2023-01-13 03:40:39 +00:00
val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm)$")
2023-01-11 18:31:20 +00:00
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]*)\\]")
2023-01-13 17:30:13 +00:00
val mentionsPattern: Pattern = Pattern.compile("@([A-Za-z0-9_-]+)")
val hashTagsPattern: Pattern = Pattern.compile("#([A-Za-z0-9_-]+)")
val urlPattern: Pattern = Patterns.WEB_URL
2023-01-11 18:31:20 +00:00
fun isValidURL(url: String?): Boolean {
return try {
URL(url).toURI()
true
} catch (e: MalformedURLException) {
false
} catch (e: URISyntaxException) {
false
}
}
2023-01-14 01:16:57 +00:00
@OptIn(ExperimentalComposeUiApi::class)
2023-01-11 18:31:20 +00:00
@Composable
2023-01-14 01:16:57 +00:00
fun RichTextViewer(content: String, tags: List<List<String>>?, note: Note, accountViewModel: AccountViewModel) {
2023-01-11 18:31:20 +00:00
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
2023-01-13 17:30:13 +00:00
val lnInvoice = LnInvoiceUtil.findInvoice(word)
if (lnInvoice != null) {
InvoicePreview(lnInvoice)
} else if (isValidURL(word)) {
2023-01-11 18:31:20 +00:00
val removedParamsFromUrl = word.split("?")[0].toLowerCase()
if (imageExtension.matcher(removedParamsFromUrl).matches()) {
2023-01-14 01:16:57 +00:00
ExtendedImageView(word)
2023-01-13 03:40:39 +00:00
} else if (videoExtension.matcher(removedParamsFromUrl).matches()) {
VideoView(word)
2023-01-11 18:31:20 +00:00
} 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)
} else {
Text(text = "$word ")
}
}
}
}
}
}
@Composable
fun TagLink(word: String, tags: List<List<String>>) {
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()
Text(
"${innerNoteState?.note?.idDisplayHex}"
)
}
} else
Text(text = "$word ")
}
}