mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	Handle no comments and comments disabled scenarios
This commit is contained in:
		| @@ -881,8 +881,7 @@ public final class VideoDetailFragment | ||||
|         tabContentDescriptions.clear(); | ||||
|  | ||||
|         if (shouldShowComments()) { | ||||
|             pageAdapter.addFragment( | ||||
|                     CommentsFragment.getInstance(serviceId, url), COMMENTS_TAB_TAG); | ||||
|             pageAdapter.addFragment(CommentsFragment.getInstance(serviceId, url), COMMENTS_TAB_TAG); | ||||
|             tabIcons.add(R.drawable.ic_comment); | ||||
|             tabContentDescriptions.add(R.string.comments_tab_description); | ||||
|         } | ||||
|   | ||||
| @@ -69,85 +69,83 @@ fun Comment(comment: CommentsInfoItem) { | ||||
|     var isExpanded by rememberSaveable { mutableStateOf(false) } | ||||
|     var showReplies by rememberSaveable { mutableStateOf(false) } | ||||
|  | ||||
|     Surface(color = MaterialTheme.colorScheme.background) { | ||||
|         Row( | ||||
|             modifier = Modifier | ||||
|                 .fillMaxWidth() | ||||
|                 .clickable { isExpanded = !isExpanded } | ||||
|                 .padding(all = 8.dp), | ||||
|             horizontalArrangement = Arrangement.spacedBy(8.dp) | ||||
|         ) { | ||||
|             if (ImageStrategy.shouldLoadImages()) { | ||||
|                 AsyncImage( | ||||
|                     model = ImageStrategy.choosePreferredImage(comment.uploaderAvatars), | ||||
|                     contentDescription = null, | ||||
|                     placeholder = painterResource(R.drawable.placeholder_person), | ||||
|                     error = painterResource(R.drawable.placeholder_person), | ||||
|                     modifier = Modifier | ||||
|                         .size(42.dp) | ||||
|                         .clip(CircleShape) | ||||
|                         .clickable { | ||||
|                             NavigationHelper.openCommentAuthorIfPresent( | ||||
|                                 context as FragmentActivity, comment | ||||
|                             ) | ||||
|                         } | ||||
|                 ) | ||||
|             } | ||||
|  | ||||
|             Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { | ||||
|                 Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { | ||||
|                     if (comment.isPinned) { | ||||
|                         Image( | ||||
|                             painter = painterResource(R.drawable.ic_pin), | ||||
|                             contentDescription = stringResource(R.string.detail_pinned_comment_view_description) | ||||
|     Row( | ||||
|         modifier = Modifier | ||||
|             .fillMaxWidth() | ||||
|             .clickable { isExpanded = !isExpanded } | ||||
|             .padding(all = 8.dp), | ||||
|         horizontalArrangement = Arrangement.spacedBy(8.dp) | ||||
|     ) { | ||||
|         if (ImageStrategy.shouldLoadImages()) { | ||||
|             AsyncImage( | ||||
|                 model = ImageStrategy.choosePreferredImage(comment.uploaderAvatars), | ||||
|                 contentDescription = null, | ||||
|                 placeholder = painterResource(R.drawable.placeholder_person), | ||||
|                 error = painterResource(R.drawable.placeholder_person), | ||||
|                 modifier = Modifier | ||||
|                     .size(42.dp) | ||||
|                     .clip(CircleShape) | ||||
|                     .clickable { | ||||
|                         NavigationHelper.openCommentAuthorIfPresent( | ||||
|                             context as FragmentActivity, comment | ||||
|                         ) | ||||
|                     } | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|                     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) | ||||
|         Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { | ||||
|             Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { | ||||
|                 if (comment.isPinned) { | ||||
|                     Image( | ||||
|                         painter = painterResource(R.drawable.ic_pin), | ||||
|                         contentDescription = stringResource(R.string.detail_pinned_comment_view_description) | ||||
|                     ) | ||||
|                 } | ||||
|  | ||||
|                 Text( | ||||
|                     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, | ||||
|                 ) | ||||
|                 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) | ||||
|             } | ||||
|  | ||||
|                 Row( | ||||
|                     modifier = Modifier.fillMaxWidth(), | ||||
|                     horizontalArrangement = Arrangement.SpaceBetween, | ||||
|                     verticalAlignment = Alignment.CenterVertically | ||||
|                 ) { | ||||
|                     Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { | ||||
|             Text( | ||||
|                 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, | ||||
|             ) | ||||
|  | ||||
|             Row( | ||||
|                 modifier = Modifier.fillMaxWidth(), | ||||
|                 horizontalArrangement = Arrangement.SpaceBetween, | ||||
|                 verticalAlignment = Alignment.CenterVertically | ||||
|             ) { | ||||
|                 Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { | ||||
|                     Image( | ||||
|                         painter = painterResource(R.drawable.ic_thumb_up), | ||||
|                         contentDescription = stringResource(R.string.detail_likes_img_view_description) | ||||
|                     ) | ||||
|                     Text(text = Localization.likeCount(context, comment.likeCount)) | ||||
|  | ||||
|                     if (comment.isHeartedByUploader) { | ||||
|                         Image( | ||||
|                             painter = painterResource(R.drawable.ic_thumb_up), | ||||
|                             contentDescription = stringResource(R.string.detail_likes_img_view_description) | ||||
|                             painter = painterResource(R.drawable.ic_heart), | ||||
|                             contentDescription = stringResource(R.string.detail_heart_img_view_description) | ||||
|                         ) | ||||
|                         Text(text = Localization.likeCount(context, comment.likeCount)) | ||||
|  | ||||
|                         if (comment.isHeartedByUploader) { | ||||
|                             Image( | ||||
|                                 painter = painterResource(R.drawable.ic_heart), | ||||
|                                 contentDescription = stringResource(R.string.detail_heart_img_view_description) | ||||
|                             ) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                     if (comment.replies != null) { | ||||
|                         TextButton(onClick = { showReplies = true }) { | ||||
|                             val text = pluralStringResource( | ||||
|                                 R.plurals.replies, comment.replyCount, comment.replyCount.toString() | ||||
|                             ) | ||||
|                             Text(text = text) | ||||
|                         } | ||||
|                 if (comment.replies != null) { | ||||
|                     TextButton(onClick = { showReplies = true }) { | ||||
|                         val text = pluralStringResource( | ||||
|                             R.plurals.replies, comment.replyCount, comment.replyCount.toString() | ||||
|                         ) | ||||
|                         Text(text = text) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -190,7 +188,7 @@ fun CommentsInfoItem( | ||||
|     this.replyCount = replyCount | ||||
| } | ||||
|  | ||||
| class DescriptionPreviewProvider : PreviewParameterProvider<Description> { | ||||
| private 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), | ||||
| @@ -214,6 +212,8 @@ private fun CommentPreview( | ||||
|     ) | ||||
|  | ||||
|     AppTheme { | ||||
|         Comment(comment) | ||||
|         Surface(color = MaterialTheme.colorScheme.background) { | ||||
|             Comment(comment) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,18 +1,33 @@ | ||||
| package org.schabi.newpipe.fragments.list.comments | ||||
|  | ||||
| import android.content.res.Configuration | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.lazy.LazyColumn | ||||
| import androidx.compose.foundation.lazy.rememberLazyListState | ||||
| import androidx.compose.material3.HorizontalDivider | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.Surface | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.derivedStateOf | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.res.stringResource | ||||
| 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.sp | ||||
| import androidx.paging.LoadState | ||||
| import androidx.paging.LoadStates | ||||
| import androidx.paging.PagingData | ||||
| import androidx.paging.compose.collectAsLazyPagingItems | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.flowOf | ||||
| import my.nanihadesuka.compose.LazyColumnScrollbar | ||||
| import my.nanihadesuka.compose.ScrollbarSettings | ||||
| 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 | ||||
| @@ -23,38 +38,81 @@ fun CommentSection( | ||||
|     parentComment: CommentsInfoItem? = null, | ||||
| ) { | ||||
|     val replies = flow.collectAsLazyPagingItems() | ||||
|     val listState = rememberLazyListState() | ||||
|     val itemCount by remember { derivedStateOf { replies.itemCount } } | ||||
|  | ||||
|     LazyColumnScrollbar(state = listState, settings = ScrollbarSettings.Default) { | ||||
|         LazyColumn(state = listState) { | ||||
|             if (parentComment != null) { | ||||
|                 item { | ||||
|                     CommentRepliesHeader(comment = parentComment) | ||||
|                     HorizontalDivider(thickness = 1.dp) | ||||
|     Surface(color = MaterialTheme.colorScheme.background) { | ||||
|         val refresh = replies.loadState.refresh | ||||
|         if (itemCount == 0 && refresh !is LoadState.Loading) { | ||||
|             NoCommentsMessage((refresh as? LoadState.Error)?.error) | ||||
|         } else { | ||||
|             val listState = rememberLazyListState() | ||||
|  | ||||
|             LazyColumnScrollbar(state = listState, settings = ScrollbarSettings.Default) { | ||||
|                 LazyColumn(state = listState) { | ||||
|                     if (parentComment != null) { | ||||
|                         item { | ||||
|                             CommentRepliesHeader(comment = parentComment) | ||||
|                             HorizontalDivider(thickness = 1.dp) | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     items(itemCount) { | ||||
|                         Comment(comment = replies[it]!!) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             items(replies.itemCount) { | ||||
|                 Comment(comment = replies[it]!!) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| private fun NoCommentsMessage(error: Throwable?) { | ||||
|     val message = if (error is CommentsDisabledException) { | ||||
|         R.string.comments_are_disabled | ||||
|     } else { | ||||
|         R.string.no_comments | ||||
|     } | ||||
|  | ||||
|     Column(horizontalAlignment = Alignment.CenterHorizontally) { | ||||
|         Text(text = "(╯°-°)╯", fontSize = 35.sp) | ||||
|         Text(text = stringResource(id = message), fontSize = 24.sp) | ||||
|     } | ||||
| } | ||||
|  | ||||
| private class CommentDataProvider : PreviewParameterProvider<PagingData<CommentsInfoItem>> { | ||||
|     private val notLoading = LoadState.NotLoading(true) | ||||
|  | ||||
|     override val values = sequenceOf( | ||||
|         // Normal view | ||||
|         PagingData.from( | ||||
|             (1..100).map { | ||||
|                 CommentsInfoItem( | ||||
|                     commentText = Description("Comment $it", Description.PLAIN_TEXT), | ||||
|                     uploaderName = "Test" | ||||
|                 ) | ||||
|             } | ||||
|         ), | ||||
|         // Comments disabled | ||||
|         PagingData.from( | ||||
|             listOf<CommentsInfoItem>(), | ||||
|             LoadStates(LoadState.Error(CommentsDisabledException()), notLoading, notLoading) | ||||
|         ), | ||||
|         // No comments | ||||
|         PagingData.from( | ||||
|             listOf<CommentsInfoItem>(), | ||||
|             LoadStates(notLoading, notLoading, notLoading) | ||||
|         ) | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) | ||||
| @Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||||
| @Composable | ||||
| private fun CommentSectionPreview() { | ||||
|     val comments = (1..100).map { | ||||
|         CommentsInfoItem( | ||||
|             commentText = Description("Comment $it", Description.PLAIN_TEXT), | ||||
|             uploaderName = "Test" | ||||
|         ) | ||||
|     } | ||||
|     val flow = flowOf(PagingData.from(comments)) | ||||
|  | ||||
| private fun CommentSectionPreview( | ||||
|     @PreviewParameter(CommentDataProvider::class) pagingData: PagingData<CommentsInfoItem> | ||||
| ) { | ||||
|     AppTheme { | ||||
|         CommentSection(flow = flow) | ||||
|         CommentSection(flow = flowOf(pagingData)) | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -25,7 +25,7 @@ class CommentsSource( | ||||
|             .subscribeOn(Schedulers.io()) | ||||
|             .map { | ||||
|                 if (it.isCommentsDisabled) { | ||||
|                     LoadResult.Invalid() | ||||
|                     LoadResult.Error(CommentsDisabledException()) | ||||
|                 } else { | ||||
|                     LoadResult.Page(it.relatedItems, null, it.nextPage) | ||||
|                 } | ||||
| @@ -34,3 +34,5 @@ class CommentsSource( | ||||
|  | ||||
|     override fun getRefreshKey(state: PagingState<Page, CommentsInfoItem>) = null | ||||
| } | ||||
|  | ||||
| class CommentsDisabledException : RuntimeException() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Isira Seneviratne
					Isira Seneviratne