mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	Use AnnotatedString to handle HTML parsing
This commit is contained in:
		| @@ -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' | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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!! | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Isira Seneviratne
					Isira Seneviratne