Improving performance of the RichText Viewer
This commit is contained in:
+6
-4
@@ -42,11 +42,13 @@ fun ExpandableRichTextViewer(
|
|||||||
) {
|
) {
|
||||||
var showFullText by remember { mutableStateOf(false) }
|
var showFullText by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Cuts the text in the first space after 350
|
val whereToCut = remember {
|
||||||
val firstSpaceAfterCut = content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
|
// Cuts the text in the first space after 350
|
||||||
val firstNewLineAfterCut = content.indexOf('\n', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
|
val firstSpaceAfterCut = content.indexOf(' ', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
|
||||||
|
val firstNewLineAfterCut = content.indexOf('\n', SHORT_TEXT_LENGTH).let { if (it < 0) content.length else it }
|
||||||
|
|
||||||
val whereToCut = minOf(firstSpaceAfterCut, firstNewLineAfterCut)
|
minOf(firstSpaceAfterCut, firstNewLineAfterCut)
|
||||||
|
}
|
||||||
|
|
||||||
val text = if (showFullText) {
|
val text = if (showFullText) {
|
||||||
content
|
content
|
||||||
|
|||||||
@@ -75,6 +75,14 @@ fun isValidURL(url: String?): Boolean {
|
|||||||
|
|
||||||
val richTextDefaults = RichTextStyle().resolveDefaults()
|
val richTextDefaults = RichTextStyle().resolveDefaults()
|
||||||
|
|
||||||
|
fun isMarkdown(content: String): Boolean {
|
||||||
|
return content.startsWith("> ") ||
|
||||||
|
content.startsWith("# ") ||
|
||||||
|
content.contains("##") ||
|
||||||
|
content.contains("__") ||
|
||||||
|
content.contains("```")
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RichTextViewer(
|
fun RichTextViewer(
|
||||||
content: String,
|
content: String,
|
||||||
@@ -85,58 +93,39 @@ fun RichTextViewer(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
|
val isMarkdown = remember { isMarkdown(content) }
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
if (content.startsWith("> ") ||
|
if (isMarkdown) {
|
||||||
content.startsWith("# ") ||
|
RenderContentAsMarkdown(content, backgroundColor)
|
||||||
content.contains("##") ||
|
|
||||||
content.contains("__") ||
|
|
||||||
content.contains("```")
|
|
||||||
) {
|
|
||||||
val myMarkDownStyle = richTextDefaults.copy(
|
|
||||||
codeBlockStyle = richTextDefaults.codeBlockStyle?.copy(
|
|
||||||
textStyle = TextStyle(
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
fontSize = 14.sp
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(0.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(shape = RoundedCornerShape(15.dp))
|
|
||||||
.border(
|
|
||||||
1.dp,
|
|
||||||
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
|
||||||
RoundedCornerShape(15.dp)
|
|
||||||
)
|
|
||||||
.background(
|
|
||||||
MaterialTheme.colors.onSurface
|
|
||||||
.copy(alpha = 0.05f)
|
|
||||||
.compositeOver(backgroundColor)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
stringStyle = richTextDefaults.stringStyle?.copy(
|
|
||||||
linkStyle = SpanStyle(
|
|
||||||
textDecoration = TextDecoration.Underline,
|
|
||||||
color = MaterialTheme.colors.primary
|
|
||||||
),
|
|
||||||
codeStyle = SpanStyle(
|
|
||||||
fontFamily = FontFamily.Monospace,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
background = MaterialTheme.colors.onSurface.copy(alpha = 0.22f).compositeOver(backgroundColor)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val markdownWithSpecialContent = returnMarkdownWithSpecialContent(content)
|
|
||||||
|
|
||||||
MaterialRichText(
|
|
||||||
style = myMarkDownStyle
|
|
||||||
) {
|
|
||||||
Markdown(
|
|
||||||
content = markdownWithSpecialContent,
|
|
||||||
markdownParseOptions = MarkdownParseOptions.Default
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichTextViewerState(
|
||||||
|
val content: String,
|
||||||
|
val urlSet: LinkedHashSet<String>,
|
||||||
|
val imagesForPager: Map<String, ZoomableUrlContent>,
|
||||||
|
val imageList: List<ZoomableUrlContent>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderRegular(
|
||||||
|
content: String,
|
||||||
|
tags: List<List<String>>?,
|
||||||
|
canPreview: Boolean,
|
||||||
|
backgroundColor: Color,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
navController: NavController
|
||||||
|
) {
|
||||||
|
var processedState by remember {
|
||||||
|
mutableStateOf<RichTextViewerState?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = content) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
val urls = UrlDetector(content, UrlDetectorOptions.Default).detect()
|
val urls = UrlDetector(content, UrlDetectorOptions.Default).detect()
|
||||||
val urlSet = urls.mapTo(LinkedHashSet(urls.size)) { it.originalUrl }
|
val urlSet = urls.mapTo(LinkedHashSet(urls.size)) { it.originalUrl }
|
||||||
val imagesForPager = urlSet.mapNotNull { fullUrl ->
|
val imagesForPager = urlSet.mapNotNull { fullUrl ->
|
||||||
@@ -151,67 +140,71 @@ fun RichTextViewer(
|
|||||||
}.associateBy { it.url }
|
}.associateBy { it.url }
|
||||||
val imageList = imagesForPager.values.toList()
|
val imageList = imagesForPager.values.toList()
|
||||||
|
|
||||||
// FlowRow doesn't work well with paragraphs. So we need to split them
|
processedState = RichTextViewerState(content, urlSet, imagesForPager, imageList)
|
||||||
content.split('\n').forEach { paragraph ->
|
}
|
||||||
FlowRow() {
|
}
|
||||||
val s = if (isArabic(paragraph)) paragraph.trim().split(' ').reversed() else paragraph.trim().split(' ')
|
|
||||||
s.forEach { word: String ->
|
// FlowRow doesn't work well with paragraphs. So we need to split them
|
||||||
if (canPreview) {
|
processedState?.let { state ->
|
||||||
// Explicit URL
|
content.split('\n').forEach { paragraph ->
|
||||||
val img = imagesForPager[word]
|
FlowRow() {
|
||||||
if (img != null) {
|
val s = if (isArabic(paragraph)) {
|
||||||
ZoomableContentView(img, imageList)
|
paragraph.trim().split(' ')
|
||||||
} else if (urlSet.contains(word)) {
|
.reversed()
|
||||||
UrlPreview(word, "$word ")
|
} else {
|
||||||
} else if (word.startsWith("lnbc", true)) {
|
paragraph.trim().split(' ')
|
||||||
MayBeInvoicePreview(word)
|
}
|
||||||
} else if (word.startsWith("lnurl", true)) {
|
s.forEach { word: String ->
|
||||||
MayBeWithdrawal(word)
|
if (canPreview) {
|
||||||
} else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) {
|
// Explicit URL
|
||||||
ClickableEmail(word)
|
val img = state.imagesForPager[word]
|
||||||
} else if (word.length > 6 && Patterns.PHONE.matcher(word).matches()) {
|
if (img != null) {
|
||||||
ClickablePhone(word)
|
ZoomableContentView(img, state.imageList)
|
||||||
} else if (isBechLink(word)) {
|
} else if (state.urlSet.contains(word)) {
|
||||||
BechLink(
|
UrlPreview(word, "$word ")
|
||||||
|
} else if (word.startsWith("lnbc", true)) {
|
||||||
|
MayBeInvoicePreview(word)
|
||||||
|
} else if (word.startsWith("lnurl", true)) {
|
||||||
|
MayBeWithdrawal(word)
|
||||||
|
} else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) {
|
||||||
|
ClickableEmail(word)
|
||||||
|
} else if (word.length > 6 && Patterns.PHONE.matcher(word).matches()) {
|
||||||
|
ClickablePhone(word)
|
||||||
|
} else if (isBechLink(word)) {
|
||||||
|
BechLink(
|
||||||
|
word,
|
||||||
|
canPreview,
|
||||||
|
backgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
navController
|
||||||
|
)
|
||||||
|
} else if (word.startsWith("#")) {
|
||||||
|
if (tagIndex.matcher(word).matches() && tags != null) {
|
||||||
|
TagLink(
|
||||||
word,
|
word,
|
||||||
|
tags,
|
||||||
canPreview,
|
canPreview,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
navController
|
navController
|
||||||
)
|
)
|
||||||
} else if (word.startsWith("#")) {
|
} else if (hashTagsPattern.matcher(word).matches()) {
|
||||||
if (tagIndex.matcher(word).matches() && tags != null) {
|
HashTag(word, navController)
|
||||||
TagLink(
|
} else {
|
||||||
word,
|
Text(
|
||||||
tags,
|
text = "$word ",
|
||||||
canPreview,
|
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||||
backgroundColor,
|
)
|
||||||
accountViewModel,
|
}
|
||||||
navController
|
} else if (noProtocolUrlValidator.matcher(word).matches()) {
|
||||||
)
|
val matcher = noProtocolUrlValidator.matcher(word)
|
||||||
} else if (hashTagsPattern.matcher(word).matches()) {
|
matcher.find()
|
||||||
HashTag(word, navController)
|
val url = matcher.group(1) // url
|
||||||
} else {
|
val additionalChars = matcher.group(4) ?: "" // additional chars
|
||||||
Text(
|
|
||||||
text = "$word ",
|
|
||||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (noProtocolUrlValidator.matcher(word).matches()) {
|
|
||||||
val matcher = noProtocolUrlValidator.matcher(word)
|
|
||||||
matcher.find()
|
|
||||||
val url = matcher.group(1) // url
|
|
||||||
val additionalChars = matcher.group(4) ?: "" // additional chars
|
|
||||||
|
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
ClickableUrl(url, "https://$url")
|
ClickableUrl(url, "https://$url")
|
||||||
Text("$additionalChars ")
|
Text("$additionalChars ")
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = "$word ",
|
|
||||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "$word ",
|
text = "$word ",
|
||||||
@@ -219,69 +212,74 @@ fun RichTextViewer(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (urlSet.contains(word)) {
|
Text(
|
||||||
ClickableUrl("$word ", word)
|
text = "$word ",
|
||||||
} else if (word.startsWith("lnurl", true)) {
|
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||||
val lnWithdrawal = LnWithdrawalUtil.findWithdrawal(word)
|
)
|
||||||
if (lnWithdrawal != null) {
|
}
|
||||||
ClickableWithdrawal(withdrawalString = lnWithdrawal)
|
} else {
|
||||||
} else {
|
if (state.urlSet.contains(word)) {
|
||||||
Text(
|
ClickableUrl("$word ", word)
|
||||||
text = "$word ",
|
} else if (word.startsWith("lnurl", true)) {
|
||||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
val lnWithdrawal = LnWithdrawalUtil.findWithdrawal(word)
|
||||||
)
|
if (lnWithdrawal != null) {
|
||||||
}
|
ClickableWithdrawal(withdrawalString = lnWithdrawal)
|
||||||
} else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) {
|
|
||||||
ClickableEmail(word)
|
|
||||||
} else if (Patterns.PHONE.matcher(word).matches() && word.length > 6) {
|
|
||||||
ClickablePhone(word)
|
|
||||||
} else if (isBechLink(word)) {
|
|
||||||
BechLink(
|
|
||||||
word,
|
|
||||||
canPreview,
|
|
||||||
backgroundColor,
|
|
||||||
accountViewModel,
|
|
||||||
navController
|
|
||||||
)
|
|
||||||
} else if (word.startsWith("#")) {
|
|
||||||
if (tagIndex.matcher(word).matches() && tags != null) {
|
|
||||||
TagLink(
|
|
||||||
word,
|
|
||||||
tags,
|
|
||||||
canPreview,
|
|
||||||
backgroundColor,
|
|
||||||
accountViewModel,
|
|
||||||
navController
|
|
||||||
)
|
|
||||||
} else if (hashTagsPattern.matcher(word).matches()) {
|
|
||||||
HashTag(word, navController)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = "$word ",
|
|
||||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (noProtocolUrlValidator.matcher(word).matches()) {
|
|
||||||
val matcher = noProtocolUrlValidator.matcher(word)
|
|
||||||
matcher.find()
|
|
||||||
val url = matcher.group(1) // url
|
|
||||||
val additionalChars = matcher.group(4) ?: "" // additional chars
|
|
||||||
|
|
||||||
if (url != null) {
|
|
||||||
ClickableUrl(url, "https://$url")
|
|
||||||
Text("$additionalChars ")
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = "$word ",
|
|
||||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "$word ",
|
text = "$word ",
|
||||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (Patterns.EMAIL_ADDRESS.matcher(word).matches()) {
|
||||||
|
ClickableEmail(word)
|
||||||
|
} else if (Patterns.PHONE.matcher(word).matches() && word.length > 6) {
|
||||||
|
ClickablePhone(word)
|
||||||
|
} else if (isBechLink(word)) {
|
||||||
|
BechLink(
|
||||||
|
word,
|
||||||
|
canPreview,
|
||||||
|
backgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
navController
|
||||||
|
)
|
||||||
|
} else if (word.startsWith("#")) {
|
||||||
|
if (tagIndex.matcher(word).matches() && tags != null) {
|
||||||
|
TagLink(
|
||||||
|
word,
|
||||||
|
tags,
|
||||||
|
canPreview,
|
||||||
|
backgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
navController
|
||||||
|
)
|
||||||
|
} else if (hashTagsPattern.matcher(word).matches()) {
|
||||||
|
HashTag(word, navController)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = "$word ",
|
||||||
|
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (noProtocolUrlValidator.matcher(word).matches()) {
|
||||||
|
val matcher = noProtocolUrlValidator.matcher(word)
|
||||||
|
matcher.find()
|
||||||
|
val url = matcher.group(1) // url
|
||||||
|
val additionalChars = matcher.group(4) ?: "" // additional chars
|
||||||
|
|
||||||
|
if (url != null) {
|
||||||
|
ClickableUrl(url, "https://$url")
|
||||||
|
Text("$additionalChars ")
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = "$word ",
|
||||||
|
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = "$word ",
|
||||||
|
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,21 +289,137 @@ fun RichTextViewer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getDisplayNameFromUserNip19(parsedNip19: Nip19.Return): String? {
|
private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) {
|
||||||
if (parsedNip19.type == Nip19.Type.USER) {
|
val myMarkDownStyle = richTextDefaults.copy(
|
||||||
val userHex = parsedNip19.hex
|
codeBlockStyle = richTextDefaults.codeBlockStyle?.copy(
|
||||||
val userBase = LocalCache.getOrCreateUser(userHex)
|
textStyle = TextStyle(
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 14.sp
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(0.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(shape = RoundedCornerShape(15.dp))
|
||||||
|
.border(
|
||||||
|
1.dp,
|
||||||
|
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||||
|
RoundedCornerShape(15.dp)
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colors.onSurface
|
||||||
|
.copy(alpha = 0.05f)
|
||||||
|
.compositeOver(backgroundColor)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
stringStyle = richTextDefaults.stringStyle?.copy(
|
||||||
|
linkStyle = SpanStyle(
|
||||||
|
textDecoration = TextDecoration.Underline,
|
||||||
|
color = MaterialTheme.colors.primary
|
||||||
|
),
|
||||||
|
codeStyle = SpanStyle(
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
background = MaterialTheme.colors.onSurface.copy(alpha = 0.22f)
|
||||||
|
.compositeOver(backgroundColor)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val userState by userBase.live().metadata.observeAsState()
|
var markdownWithSpecialContent by remember { mutableStateOf<String?>(null) }
|
||||||
val displayName = userState?.user?.bestDisplayName()
|
var nip19References by remember { mutableStateOf<List<Nip19.Return>>(emptyList()) }
|
||||||
if (displayName !== null) {
|
var refresh by remember { mutableStateOf(0) }
|
||||||
return displayName
|
|
||||||
|
LaunchedEffect(key1 = content) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
nip19References = returnNIP19References(content)
|
||||||
|
markdownWithSpecialContent = returnMarkdownWithSpecialContent(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = refresh) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val newMarkdownWithSpecialContent = returnMarkdownWithSpecialContent(content)
|
||||||
|
if (markdownWithSpecialContent != newMarkdownWithSpecialContent) {
|
||||||
|
markdownWithSpecialContent = newMarkdownWithSpecialContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nip19References.forEach {
|
||||||
|
var baseUser by remember { mutableStateOf<User?>(null) }
|
||||||
|
var baseNote by remember { mutableStateOf<Note?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = it.hex) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
|
||||||
|
LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
|
||||||
|
baseNote = note
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.type == Nip19.Type.USER) {
|
||||||
|
LocalCache.checkGetOrCreateUser(it.hex)?.let { user ->
|
||||||
|
baseUser = user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseNote?.let {
|
||||||
|
val noteState by it.live().metadata.observeAsState()
|
||||||
|
if (noteState?.note?.event != null) {
|
||||||
|
refresh++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
baseUser?.let {
|
||||||
|
val userState by it.live().metadata.observeAsState()
|
||||||
|
if (userState?.user?.info != null) {
|
||||||
|
refresh++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
markdownWithSpecialContent?.let {
|
||||||
|
MaterialRichText(
|
||||||
|
style = myMarkDownStyle
|
||||||
|
) {
|
||||||
|
Markdown(
|
||||||
|
content = it,
|
||||||
|
markdownParseOptions = MarkdownParseOptions.Default
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
private fun getDisplayNameFromNip19(nip19: Nip19.Return): String? {
|
||||||
|
if (nip19.type == Nip19.Type.USER) {
|
||||||
|
return LocalCache.users[nip19.hex]?.bestDisplayName()
|
||||||
|
} else if (nip19.type == Nip19.Type.NOTE) {
|
||||||
|
return LocalCache.notes[nip19.hex]?.idDisplayNote()
|
||||||
|
} else if (nip19.type == Nip19.Type.ADDRESS) {
|
||||||
|
return LocalCache.addressables[nip19.hex]?.idDisplayNote()
|
||||||
|
} else if (nip19.type == Nip19.Type.EVENT) {
|
||||||
|
return LocalCache.notes[nip19.hex]?.idDisplayNote() ?: LocalCache.addressables[nip19.hex]?.idDisplayNote()
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun returnNIP19References(content: String): List<Nip19.Return> {
|
||||||
|
val listOfReferences = mutableListOf<Nip19.Return>()
|
||||||
|
content.split('\n').forEach { paragraph ->
|
||||||
|
paragraph.split(' ').forEach { word: String ->
|
||||||
|
if (isBechLink(word)) {
|
||||||
|
val parsedNip19 = Nip19.uriToRoute(word)
|
||||||
|
parsedNip19?.let {
|
||||||
|
listOfReferences.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listOfReferences
|
||||||
|
}
|
||||||
|
|
||||||
private fun returnMarkdownWithSpecialContent(content: String): String {
|
private fun returnMarkdownWithSpecialContent(content: String): String {
|
||||||
var returnContent = ""
|
var returnContent = ""
|
||||||
content.split('\n').forEach { paragraph ->
|
content.split('\n').forEach { paragraph ->
|
||||||
@@ -319,7 +433,7 @@ private fun returnMarkdownWithSpecialContent(content: String): String {
|
|||||||
} else if (isBechLink(word)) {
|
} else if (isBechLink(word)) {
|
||||||
val parsedNip19 = Nip19.uriToRoute(word)
|
val parsedNip19 = Nip19.uriToRoute(word)
|
||||||
returnContent += if (parsedNip19 !== null) {
|
returnContent += if (parsedNip19 !== null) {
|
||||||
val displayName = getDisplayNameFromUserNip19(parsedNip19)
|
val displayName = getDisplayNameFromNip19(parsedNip19)
|
||||||
if (displayName != null) {
|
if (displayName != null) {
|
||||||
"[@$displayName](nostr://$word) "
|
"[@$displayName](nostr://$word) "
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -209,7 +209,9 @@ private fun LocalImageView(
|
|||||||
mutableStateOf<AsyncImagePainter.State?>(null)
|
mutableStateOf<AsyncImagePainter.State?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val ratio = aspectRatio(content.dim)
|
val ratio = remember {
|
||||||
|
aspectRatio(content.dim)
|
||||||
|
}
|
||||||
|
|
||||||
BoxWithConstraints(contentAlignment = Alignment.Center) {
|
BoxWithConstraints(contentAlignment = Alignment.Center) {
|
||||||
val myModifier = mainImageModifier.also {
|
val myModifier = mainImageModifier.also {
|
||||||
@@ -257,6 +259,7 @@ private fun UrlImageView(
|
|||||||
content: ZoomableUrlImage,
|
content: ZoomableUrlImage,
|
||||||
mainImageModifier: Modifier
|
mainImageModifier: Modifier
|
||||||
) {
|
) {
|
||||||
|
println("UrlImageView")
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -269,7 +272,9 @@ private fun UrlImageView(
|
|||||||
mutableStateOf<Boolean?>(null)
|
mutableStateOf<Boolean?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val ratio = aspectRatio(content.dim)
|
val ratio = remember {
|
||||||
|
aspectRatio(content.dim)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = content.url, key2 = imageState) {
|
LaunchedEffect(key1 = content.url, key2 = imageState) {
|
||||||
if (imageState is AsyncImagePainter.State.Success) {
|
if (imageState is AsyncImagePainter.State.Success) {
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
|
|||||||
val noteState by messageSetCard.note.live().metadata.observeAsState()
|
val noteState by messageSetCard.note.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
val noteEvent = note?.event
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -50,10 +49,14 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
|
|||||||
|
|
||||||
LaunchedEffect(key1 = messageSetCard.createdAt()) {
|
LaunchedEffect(key1 = messageSetCard.createdAt()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
isNew =
|
val newIsNew =
|
||||||
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
||||||
|
|
||||||
NotificationCache.markAsRead(routeForLastRead, messageSetCard.createdAt())
|
NotificationCache.markAsRead(routeForLastRead, messageSetCard.createdAt())
|
||||||
|
|
||||||
|
if (newIsNew != isNew) {
|
||||||
|
isNew = newIsNew
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,9 +68,13 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
|
|||||||
|
|
||||||
LaunchedEffect(key1 = multiSetCard.createdAt()) {
|
LaunchedEffect(key1 = multiSetCard.createdAt()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
val newIsNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
||||||
|
|
||||||
NotificationCache.markAsRead(routeForLastRead, multiSetCard.createdAt)
|
NotificationCache.markAsRead(routeForLastRead, multiSetCard.createdAt)
|
||||||
|
|
||||||
|
if (newIsNew != isNew) {
|
||||||
|
isNew = newIsNew
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,11 @@ fun NoteComposeInner(
|
|||||||
val createdAt = note.createdAt()
|
val createdAt = note.createdAt()
|
||||||
if (createdAt != null) {
|
if (createdAt != null) {
|
||||||
NotificationCache.markAsRead(it, createdAt)
|
NotificationCache.markAsRead(it, createdAt)
|
||||||
isNew = createdAt > lastTime
|
|
||||||
|
val newIsNew = createdAt > lastTime
|
||||||
|
if (newIsNew != isNew) {
|
||||||
|
isNew = newIsNew
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,7 +424,8 @@ private fun RenderTextEvent(
|
|||||||
) {
|
) {
|
||||||
val noteEvent = note.event ?: return
|
val noteEvent = note.event ?: return
|
||||||
|
|
||||||
val eventContent = accountViewModel.decrypt(note)
|
val eventContent = remember { accountViewModel.decrypt(note) }
|
||||||
|
val modifier = remember { Modifier.fillMaxWidth() }
|
||||||
|
|
||||||
if (eventContent != null) {
|
if (eventContent != null) {
|
||||||
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
|
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
|
||||||
@@ -434,7 +439,7 @@ private fun RenderTextEvent(
|
|||||||
TranslatableRichTextViewer(
|
TranslatableRichTextViewer(
|
||||||
content = eventContent,
|
content = eventContent,
|
||||||
canPreview = canPreview && !makeItShort,
|
canPreview = canPreview && !makeItShort,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = modifier,
|
||||||
tags = noteEvent.tags(),
|
tags = noteEvent.tags(),
|
||||||
backgroundColor = backgroundColor,
|
backgroundColor = backgroundColor,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
@@ -548,10 +553,10 @@ private fun RenderPrivateMessage(
|
|||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event as? PrivateDmEvent ?: return
|
val noteEvent = note.event as? PrivateDmEvent ?: return
|
||||||
val withMe = noteEvent.with(accountViewModel.userProfile().pubkeyHex)
|
val withMe = remember { noteEvent.with(accountViewModel.userProfile().pubkeyHex) }
|
||||||
|
|
||||||
if (withMe) {
|
if (withMe) {
|
||||||
val eventContent = accountViewModel.decrypt(note)
|
val eventContent = remember { accountViewModel.decrypt(note) }
|
||||||
|
|
||||||
if (eventContent != null) {
|
if (eventContent != null) {
|
||||||
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
|
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
|
||||||
@@ -908,7 +913,7 @@ fun TimeAgo(time: Long) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) {
|
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) {
|
||||||
val baseChannel = baseNote.channel()
|
val baseChannel = remember { baseNote.channel() }
|
||||||
|
|
||||||
Column(Modifier.width(55.dp)) {
|
Column(Modifier.width(55.dp)) {
|
||||||
// Draws the boosted picture outside the boosted card.
|
// Draws the boosted picture outside the boosted card.
|
||||||
@@ -1002,7 +1007,13 @@ fun DisplayHighlight(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val quote = highlight.split("\n").map { "> *${it.removeSuffix(" ")}*" }.joinToString("\n")
|
val quote =
|
||||||
|
remember {
|
||||||
|
highlight
|
||||||
|
.split("\n")
|
||||||
|
.map { "> *${it.removeSuffix(" ")}*" }
|
||||||
|
.joinToString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
TranslatableRichTextViewer(
|
TranslatableRichTextViewer(
|
||||||
quote,
|
quote,
|
||||||
@@ -1205,9 +1216,9 @@ fun BadgeDisplay(baseNote: Note) {
|
|||||||
val background = MaterialTheme.colors.background
|
val background = MaterialTheme.colors.background
|
||||||
val badgeData = baseNote.event as? BadgeDefinitionEvent ?: return
|
val badgeData = baseNote.event as? BadgeDefinitionEvent ?: return
|
||||||
|
|
||||||
val image = badgeData.image()
|
val image = remember { badgeData.image() }
|
||||||
val name = badgeData.name()
|
val name = remember { badgeData.name() }
|
||||||
val description = badgeData.description()
|
val description = remember { badgeData.description() }
|
||||||
|
|
||||||
var backgroundFromImage by remember { mutableStateOf(Pair(background, background)) }
|
var backgroundFromImage by remember { mutableStateOf(Pair(background, background)) }
|
||||||
var imageResult by remember { mutableStateOf<AsyncImagePainter.State.Success?>(null) }
|
var imageResult by remember { mutableStateOf<AsyncImagePainter.State.Success?>(null) }
|
||||||
|
|||||||
-2
@@ -73,8 +73,6 @@ fun TranslatableRichTextViewer(
|
|||||||
showOriginal = preference == task.result.sourceLang
|
showOriginal = preference == task.result.sourceLang
|
||||||
}
|
}
|
||||||
translatedTextState.value = task.result
|
translatedTextState.value = task.result
|
||||||
} else {
|
|
||||||
translatedTextState.value = ResultOrError(content, null, null, null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user