mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-01-09 17:00:32 +00:00
Use AnnotatedString to handle HTML parsing
This commit is contained in:
parent
e75eb2d544
commit
c29fa70080
@ -293,6 +293,7 @@ dependencies {
|
|||||||
implementation 'androidx.compose.material3:material3'
|
implementation 'androidx.compose.material3:material3'
|
||||||
implementation 'androidx.activity:activity-compose'
|
implementation 'androidx.activity:activity-compose'
|
||||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||||
|
implementation 'androidx.compose.ui:ui-text:1.7.0-beta03' // Needed for parsing HTML to AnnotatedString
|
||||||
|
|
||||||
// Paging
|
// Paging
|
||||||
implementation 'androidx.paging:paging-rxjava3:3.3.0'
|
implementation 'androidx.paging:paging-rxjava3:3.3.0'
|
||||||
|
@ -18,6 +18,7 @@ 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.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
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.compose.ui.unit.dp
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import coil.compose.AsyncImage
|
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.NavigationHelper
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy
|
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
|
@Composable
|
||||||
fun Comment(comment: CommentsInfoItem) {
|
fun Comment(comment: CommentsInfoItem) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -79,23 +97,22 @@ fun Comment(comment: CommentsInfoItem) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val date = Localization.relativeTimeOrTextual(
|
val nameAndDate = remember(comment) {
|
||||||
context, comment.uploadDate, comment.textualUploadDate
|
val date = Localization.relativeTimeOrTextual(
|
||||||
)
|
context, comment.uploadDate, comment.textualUploadDate
|
||||||
Text(
|
)
|
||||||
text = Localization.concatenateStrings(comment.uploaderName, date),
|
Localization.concatenateStrings(comment.uploaderName, date)
|
||||||
color = MaterialTheme.colorScheme.secondary
|
}
|
||||||
)
|
Text(text = nameAndDate, color = MaterialTheme.colorScheme.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle HTML and Markdown formats.
|
|
||||||
Text(
|
Text(
|
||||||
text = comment.commentText.content,
|
text = rememberParsedText(comment.commentText),
|
||||||
// If the comment is expanded, we display all its content
|
// If the comment is expanded, we display all its content
|
||||||
// otherwise we only display the first two lines
|
// otherwise we only display the first two lines
|
||||||
maxLines = if (isExpanded) Int.MAX_VALUE else 2,
|
maxLines = if (isExpanded) Int.MAX_VALUE else 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
@ -138,12 +155,21 @@ fun CommentsInfoItem(
|
|||||||
this.isPinned = isPinned
|
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 = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
@Composable
|
@Composable
|
||||||
private fun CommentPreview() {
|
private fun CommentPreview(
|
||||||
|
@PreviewParameter(DescriptionPreviewProvider::class) description: Description
|
||||||
|
) {
|
||||||
val comment = CommentsInfoItem(
|
val comment = CommentsInfoItem(
|
||||||
commentText = Description("Hello world!\n\nThis line should be hidden by default.", Description.PLAIN_TEXT),
|
commentText = description,
|
||||||
uploaderName = "Test",
|
uploaderName = "Test",
|
||||||
likeCount = 100,
|
likeCount = 100,
|
||||||
isPinned = true,
|
isPinned = true,
|
||||||
|
@ -8,7 +8,6 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
|
||||||
@ -18,14 +17,13 @@ import org.schabi.newpipe.ui.theme.AppTheme
|
|||||||
@Composable
|
@Composable
|
||||||
fun CommentReplies(
|
fun CommentReplies(
|
||||||
comment: CommentsInfoItem,
|
comment: CommentsInfoItem,
|
||||||
flow: Flow<PagingData<CommentsInfoItem>>,
|
flow: Flow<PagingData<CommentsInfoItem>>
|
||||||
disposables: CompositeDisposable
|
|
||||||
) {
|
) {
|
||||||
val replies = flow.collectAsLazyPagingItems()
|
val replies = flow.collectAsLazyPagingItems()
|
||||||
|
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
item {
|
item {
|
||||||
CommentRepliesHeader(comment = comment, disposables = disposables)
|
CommentRepliesHeader(comment = comment)
|
||||||
HorizontalDivider(thickness = 1.dp)
|
HorizontalDivider(thickness = 1.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +56,6 @@ private fun CommentRepliesPreview() {
|
|||||||
val flow = flowOf(PagingData.from(listOf(reply1, reply2)))
|
val flow = flowOf(PagingData.from(listOf(reply1, reply2)))
|
||||||
|
|
||||||
AppTheme {
|
AppTheme {
|
||||||
CommentReplies(comment = comment, flow = flow, disposables = CompositeDisposable())
|
CommentReplies(comment = comment, flow = flow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,11 @@ import androidx.compose.ui.platform.ComposeView
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
|
||||||
import org.schabi.newpipe.ktx.serializable
|
import org.schabi.newpipe.ktx.serializable
|
||||||
import org.schabi.newpipe.ui.theme.AppTheme
|
import org.schabi.newpipe.ui.theme.AppTheme
|
||||||
|
|
||||||
class CommentRepliesFragment : Fragment() {
|
class CommentRepliesFragment : Fragment() {
|
||||||
private val disposables = CompositeDisposable()
|
|
||||||
lateinit var comment: CommentsInfoItem
|
lateinit var comment: CommentsInfoItem
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@ -33,17 +31,12 @@ class CommentRepliesFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppTheme {
|
AppTheme {
|
||||||
CommentReplies(comment = comment, flow = flow, disposables = disposables)
|
CommentReplies(comment = comment, flow = flow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
disposables.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField
|
@JvmField
|
||||||
val TAG = CommentRepliesFragment::class.simpleName!!
|
val TAG = CommentRepliesFragment::class.simpleName!!
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.schabi.newpipe.fragments.list.comments
|
package org.schabi.newpipe.fragments.list.comments
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
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.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
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 androidx.fragment.app.FragmentActivity
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
|
||||||
import org.schabi.newpipe.extractor.stream.Description
|
import org.schabi.newpipe.extractor.stream.Description
|
||||||
import org.schabi.newpipe.ui.theme.AppTheme
|
import org.schabi.newpipe.ui.theme.AppTheme
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import org.schabi.newpipe.util.NavigationHelper
|
import org.schabi.newpipe.util.NavigationHelper
|
||||||
import org.schabi.newpipe.util.ServiceHelper
|
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy
|
import org.schabi.newpipe.util.image.ImageStrategy
|
||||||
import org.schabi.newpipe.util.text.TextLinkifier
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommentRepliesHeader(comment: CommentsInfoItem, disposables: CompositeDisposable) {
|
fun CommentRepliesHeader(comment: CommentsInfoItem) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
@ -117,20 +110,9 @@ fun CommentRepliesHeader(comment: CommentsInfoItem, disposables: CompositeDispos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidView(
|
Text(
|
||||||
factory = { context ->
|
text = rememberParsedText(comment.commentText),
|
||||||
TextView(context).apply {
|
style = MaterialTheme.typography.bodyMedium
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,6 +131,6 @@ fun CommentRepliesHeaderPreview() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
AppTheme {
|
AppTheme {
|
||||||
CommentRepliesHeader(comment, CompositeDisposable())
|
CommentRepliesHeader(comment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user