1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-01-09 08:50:34 +00:00

Use AnnotatedString to handle HTML parsing

This commit is contained in:
Isira Seneviratne 2024-06-19 12:40:49 +05:30
parent e75eb2d544
commit c29fa70080
5 changed files with 48 additions and 48 deletions

View File

@ -293,6 +293,7 @@ dependencies {
implementation 'androidx.compose.material3:material3'
implementation 'androidx.activity:activity-compose'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.ui:ui-text:1.7.0-beta03' // Needed for parsing HTML to AnnotatedString
// Paging
implementation 'androidx.paging:paging-rxjava3:3.3.0'

View File

@ -18,6 +18,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@ -25,8 +26,13 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ParagraphStyle
import androidx.compose.ui.text.fromHtml
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import coil.compose.AsyncImage
@ -38,6 +44,18 @@ import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.image.ImageStrategy
@Composable
fun rememberParsedText(commentText: Description): AnnotatedString {
// TODO: Handle links and hashtags, Markdown.
return remember(commentText) {
if (commentText.type == Description.HTML) {
AnnotatedString.fromHtml(commentText.content)
} else {
AnnotatedString(commentText.content, ParagraphStyle())
}
}
}
@Composable
fun Comment(comment: CommentsInfoItem) {
val context = LocalContext.current
@ -79,23 +97,22 @@ fun Comment(comment: CommentsInfoItem) {
)
}
val date = Localization.relativeTimeOrTextual(
context, comment.uploadDate, comment.textualUploadDate
)
Text(
text = Localization.concatenateStrings(comment.uploaderName, date),
color = MaterialTheme.colorScheme.secondary
)
val nameAndDate = remember(comment) {
val date = Localization.relativeTimeOrTextual(
context, comment.uploadDate, comment.textualUploadDate
)
Localization.concatenateStrings(comment.uploaderName, date)
}
Text(text = nameAndDate, color = MaterialTheme.colorScheme.secondary)
}
// TODO: Handle HTML and Markdown formats.
Text(
text = comment.commentText.content,
text = rememberParsedText(comment.commentText),
// If the comment is expanded, we display all its content
// otherwise we only display the first two lines
maxLines = if (isExpanded) Int.MAX_VALUE else 2,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodyMedium
style = MaterialTheme.typography.bodyMedium,
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
@ -138,12 +155,21 @@ fun CommentsInfoItem(
this.isPinned = isPinned
}
class DescriptionPreviewProvider : PreviewParameterProvider<Description> {
override val values = sequenceOf(
Description("Hello world!<br><br>This line should be hidden by default.", Description.HTML),
Description("Hello world!\n\nThis line should be hidden by default.", Description.PLAIN_TEXT),
)
}
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun CommentPreview() {
private fun CommentPreview(
@PreviewParameter(DescriptionPreviewProvider::class) description: Description
) {
val comment = CommentsInfoItem(
commentText = Description("Hello world!\n\nThis line should be hidden by default.", Description.PLAIN_TEXT),
commentText = description,
uploaderName = "Test",
likeCount = 100,
isPinned = true,

View File

@ -8,7 +8,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
@ -18,14 +17,13 @@ import org.schabi.newpipe.ui.theme.AppTheme
@Composable
fun CommentReplies(
comment: CommentsInfoItem,
flow: Flow<PagingData<CommentsInfoItem>>,
disposables: CompositeDisposable
flow: Flow<PagingData<CommentsInfoItem>>
) {
val replies = flow.collectAsLazyPagingItems()
LazyColumn {
item {
CommentRepliesHeader(comment = comment, disposables = disposables)
CommentRepliesHeader(comment = comment)
HorizontalDivider(thickness = 1.dp)
}
@ -58,6 +56,6 @@ private fun CommentRepliesPreview() {
val flow = flowOf(PagingData.from(listOf(reply1, reply2)))
AppTheme {
CommentReplies(comment = comment, flow = flow, disposables = CompositeDisposable())
CommentReplies(comment = comment, flow = flow)
}
}

View File

@ -9,13 +9,11 @@ import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.Fragment
import androidx.paging.Pager
import androidx.paging.PagingConfig
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
import org.schabi.newpipe.ktx.serializable
import org.schabi.newpipe.ui.theme.AppTheme
class CommentRepliesFragment : Fragment() {
private val disposables = CompositeDisposable()
lateinit var comment: CommentsInfoItem
override fun onCreateView(
@ -33,17 +31,12 @@ class CommentRepliesFragment : Fragment() {
}
AppTheme {
CommentReplies(comment = comment, flow = flow, disposables = disposables)
CommentReplies(comment = comment, flow = flow)
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
disposables.clear()
}
companion object {
@JvmField
val TAG = CommentRepliesFragment::class.simpleName!!

View File

@ -1,7 +1,6 @@
package org.schabi.newpipe.fragments.list.comments
import android.content.res.Configuration
import android.widget.TextView
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -25,24 +24,18 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.text.HtmlCompat
import androidx.core.text.method.LinkMovementMethodCompat
import androidx.fragment.app.FragmentActivity
import coil.compose.AsyncImage
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
import org.schabi.newpipe.extractor.stream.Description
import org.schabi.newpipe.ui.theme.AppTheme
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.image.ImageStrategy
import org.schabi.newpipe.util.text.TextLinkifier
@Composable
fun CommentRepliesHeader(comment: CommentsInfoItem, disposables: CompositeDisposable) {
fun CommentRepliesHeader(comment: CommentsInfoItem) {
val context = LocalContext.current
Surface(color = MaterialTheme.colorScheme.background) {
@ -117,20 +110,9 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, disposables: CompositeDispos
}
}
AndroidView(
factory = { context ->
TextView(context).apply {
movementMethod = LinkMovementMethodCompat.getInstance()
}
},
update = { view ->
// setup comment content
TextLinkifier.fromDescription(
view, comment.commentText, HtmlCompat.FROM_HTML_MODE_LEGACY,
ServiceHelper.getServiceById(comment.serviceId), comment.url, disposables,
null
)
}
Text(
text = rememberParsedText(comment.commentText),
style = MaterialTheme.typography.bodyMedium
)
}
}
@ -149,6 +131,6 @@ fun CommentRepliesHeaderPreview() {
)
AppTheme {
CommentRepliesHeader(comment, CompositeDisposable())
CommentRepliesHeader(comment)
}
}