mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-14 15:17:05 +05:30
refactor(comments): convert internal representation to a tree
This commit is contained in:
parent
0d668ac3aa
commit
18d82a5eb3
3 changed files with 86 additions and 14 deletions
|
@ -26,10 +26,8 @@ import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
@ -43,7 +41,6 @@ import dev.msfjarvis.claw.common.posts.TagRow
|
||||||
import dev.msfjarvis.claw.common.res.ClawIcons
|
import dev.msfjarvis.claw.common.res.ClawIcons
|
||||||
import dev.msfjarvis.claw.common.ui.NetworkImage
|
import dev.msfjarvis.claw.common.ui.NetworkImage
|
||||||
import dev.msfjarvis.claw.common.ui.ThemedRichText
|
import dev.msfjarvis.claw.common.ui.ThemedRichText
|
||||||
import dev.msfjarvis.claw.model.Comment
|
|
||||||
import dev.msfjarvis.claw.model.LinkMetadata
|
import dev.msfjarvis.claw.model.LinkMetadata
|
||||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
import dev.msfjarvis.claw.model.LobstersPostDetails
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
@ -134,17 +131,18 @@ private val CommentEntryPadding = 16f.dp
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CommentEntry(
|
fun CommentEntry(
|
||||||
comment: Comment,
|
commentNode: CommentNode,
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
|
toggleExpanded: (CommentNode) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
var expanded by remember(comment) { mutableStateOf(true) }
|
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
|
val comment = commentNode.comment
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { expanded = !expanded }
|
.clickable { toggleExpanded(commentNode) }
|
||||||
.background(MaterialTheme.colorScheme.background)
|
.background(MaterialTheme.colorScheme.background)
|
||||||
.padding(
|
.padding(
|
||||||
start = CommentEntryPadding * comment.indentLevel,
|
start = CommentEntryPadding * comment.indentLevel,
|
||||||
|
@ -167,7 +165,7 @@ fun CommentEntry(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.clickable { uriHandler.openUri("https://lobste.rs/u/${comment.user.username}") },
|
Modifier.clickable { uriHandler.openUri("https://lobste.rs/u/${comment.user.username}") },
|
||||||
)
|
)
|
||||||
AnimatedContent(targetState = expanded) { expandedState ->
|
AnimatedContent(targetState = commentNode.isExpanded) { expandedState ->
|
||||||
if (expandedState) {
|
if (expandedState) {
|
||||||
ThemedRichText(
|
ThemedRichText(
|
||||||
text = htmlConverter.convertHTMLToMarkdown(comment.comment),
|
text = htmlConverter.convertHTMLToMarkdown(comment.comment),
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2022 Harsh Shandilya.
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
package dev.msfjarvis.claw.common.comments
|
||||||
|
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import dev.msfjarvis.claw.model.Comment
|
||||||
|
|
||||||
|
data class CommentNode(
|
||||||
|
val comment: Comment,
|
||||||
|
var parent: CommentNode? = null,
|
||||||
|
val children: MutableList<CommentNode> = mutableListOf(),
|
||||||
|
var isExpanded: Boolean = true
|
||||||
|
) {
|
||||||
|
fun addChild(child: CommentNode) {
|
||||||
|
if (comment.indentLevel + 1 == child.comment.indentLevel) {
|
||||||
|
children.add(child)
|
||||||
|
child.parent = this
|
||||||
|
} else {
|
||||||
|
children.last().addChild(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createListNode(comments: List<Comment>): MutableList<CommentNode> {
|
||||||
|
val commentNodes = mutableListOf<CommentNode>()
|
||||||
|
|
||||||
|
for (i in comments.indices) {
|
||||||
|
if (comments[i].indentLevel == 1) {
|
||||||
|
commentNodes.add(CommentNode(comment = comments[i]))
|
||||||
|
} else {
|
||||||
|
commentNodes.last().addChild(CommentNode(comment = comments[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return commentNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayListNode(
|
||||||
|
comments: List<CommentNode>,
|
||||||
|
htmlConverter: HTMLConverter,
|
||||||
|
updateComments: (CommentNode) -> Unit,
|
||||||
|
) {
|
||||||
|
comments.forEach {
|
||||||
|
CommentEntry(commentNode = it, htmlConverter = htmlConverter, updateComments)
|
||||||
|
Divider()
|
||||||
|
|
||||||
|
if (it.children.isNotEmpty()) {
|
||||||
|
DisplayListNode(comments = it.children, htmlConverter = htmlConverter, updateComments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleAllExpanded(commentNode: CommentNode): CommentNode {
|
||||||
|
commentNode.isExpanded = !commentNode.isExpanded
|
||||||
|
|
||||||
|
if (commentNode.children.isNotEmpty()) {
|
||||||
|
commentNode.children.forEach { toggleAllExpanded(it) }
|
||||||
|
}
|
||||||
|
return commentNode
|
||||||
|
}
|
|
@ -11,14 +11,13 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
|
||||||
import androidx.compose.material3.Divider
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
@ -39,6 +38,8 @@ private fun CommentsPageInternal(
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val commentNodes = createListNode(details.comments).toMutableStateList()
|
||||||
|
|
||||||
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
|
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
|
||||||
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
|
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
|
||||||
item {
|
item {
|
||||||
|
@ -49,7 +50,7 @@ private fun CommentsPageInternal(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.comments.isNotEmpty()) {
|
if (commentNodes.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
text = "Comments",
|
text = "Comments",
|
||||||
|
@ -58,11 +59,18 @@ private fun CommentsPageInternal(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsIndexed(details.comments) { index, item ->
|
item {
|
||||||
if (index != 0) {
|
DisplayListNode(comments = commentNodes, htmlConverter = htmlConverter) { node ->
|
||||||
Divider()
|
val newNode = toggleAllExpanded(node)
|
||||||
|
val index =
|
||||||
|
commentNodes.indexOf(commentNodes.find { it.comment.url == node.comment.url })
|
||||||
|
if (index == -1) {
|
||||||
|
error("Failed to find node for comment: ${node.comment}")
|
||||||
|
} else {
|
||||||
|
commentNodes.removeAt(index)
|
||||||
|
commentNodes.add(index, newNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CommentEntry(comment = item, htmlConverter = htmlConverter)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item {
|
item {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue