mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 07:13: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(); |         tabContentDescriptions.clear(); | ||||||
|  |  | ||||||
|         if (shouldShowComments()) { |         if (shouldShowComments()) { | ||||||
|             pageAdapter.addFragment( |             pageAdapter.addFragment(CommentsFragment.getInstance(serviceId, url), COMMENTS_TAB_TAG); | ||||||
|                     CommentsFragment.getInstance(serviceId, url), COMMENTS_TAB_TAG); |  | ||||||
|             tabIcons.add(R.drawable.ic_comment); |             tabIcons.add(R.drawable.ic_comment); | ||||||
|             tabContentDescriptions.add(R.string.comments_tab_description); |             tabContentDescriptions.add(R.string.comments_tab_description); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -69,85 +69,83 @@ fun Comment(comment: CommentsInfoItem) { | |||||||
|     var isExpanded by rememberSaveable { mutableStateOf(false) } |     var isExpanded by rememberSaveable { mutableStateOf(false) } | ||||||
|     var showReplies by rememberSaveable { mutableStateOf(false) } |     var showReplies by rememberSaveable { mutableStateOf(false) } | ||||||
|  |  | ||||||
|     Surface(color = MaterialTheme.colorScheme.background) { |     Row( | ||||||
|         Row( |         modifier = Modifier | ||||||
|             modifier = Modifier |             .fillMaxWidth() | ||||||
|                 .fillMaxWidth() |             .clickable { isExpanded = !isExpanded } | ||||||
|                 .clickable { isExpanded = !isExpanded } |             .padding(all = 8.dp), | ||||||
|                 .padding(all = 8.dp), |         horizontalArrangement = Arrangement.spacedBy(8.dp) | ||||||
|             horizontalArrangement = Arrangement.spacedBy(8.dp) |     ) { | ||||||
|         ) { |         if (ImageStrategy.shouldLoadImages()) { | ||||||
|             if (ImageStrategy.shouldLoadImages()) { |             AsyncImage( | ||||||
|                 AsyncImage( |                 model = ImageStrategy.choosePreferredImage(comment.uploaderAvatars), | ||||||
|                     model = ImageStrategy.choosePreferredImage(comment.uploaderAvatars), |                 contentDescription = null, | ||||||
|                     contentDescription = null, |                 placeholder = painterResource(R.drawable.placeholder_person), | ||||||
|                     placeholder = painterResource(R.drawable.placeholder_person), |                 error = painterResource(R.drawable.placeholder_person), | ||||||
|                     error = painterResource(R.drawable.placeholder_person), |                 modifier = Modifier | ||||||
|                     modifier = Modifier |                     .size(42.dp) | ||||||
|                         .size(42.dp) |                     .clip(CircleShape) | ||||||
|                         .clip(CircleShape) |                     .clickable { | ||||||
|                         .clickable { |                         NavigationHelper.openCommentAuthorIfPresent( | ||||||
|                             NavigationHelper.openCommentAuthorIfPresent( |                             context as FragmentActivity, comment | ||||||
|                                 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) |  | ||||||
|                         ) |                         ) | ||||||
|                     } |                     } | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|                     val nameAndDate = remember(comment) { |         Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { | ||||||
|                         val date = Localization.relativeTimeOrTextual( |             Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { | ||||||
|                             context, comment.uploadDate, comment.textualUploadDate |                 if (comment.isPinned) { | ||||||
|                         ) |                     Image( | ||||||
|                         Localization.concatenateStrings(comment.uploaderName, date) |                         painter = painterResource(R.drawable.ic_pin), | ||||||
|                     } |                         contentDescription = stringResource(R.string.detail_pinned_comment_view_description) | ||||||
|                     Text(text = nameAndDate, color = MaterialTheme.colorScheme.secondary) |                     ) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 Text( |                 val nameAndDate = remember(comment) { | ||||||
|                     text = rememberParsedText(comment.commentText), |                     val date = Localization.relativeTimeOrTextual( | ||||||
|                     // If the comment is expanded, we display all its content |                         context, comment.uploadDate, comment.textualUploadDate | ||||||
|                     // otherwise we only display the first two lines |                     ) | ||||||
|                     maxLines = if (isExpanded) Int.MAX_VALUE else 2, |                     Localization.concatenateStrings(comment.uploaderName, date) | ||||||
|                     overflow = TextOverflow.Ellipsis, |                 } | ||||||
|                     style = MaterialTheme.typography.bodyMedium, |                 Text(text = nameAndDate, color = MaterialTheme.colorScheme.secondary) | ||||||
|                 ) |             } | ||||||
|  |  | ||||||
|                 Row( |             Text( | ||||||
|                     modifier = Modifier.fillMaxWidth(), |                 text = rememberParsedText(comment.commentText), | ||||||
|                     horizontalArrangement = Arrangement.SpaceBetween, |                 // If the comment is expanded, we display all its content | ||||||
|                     verticalAlignment = Alignment.CenterVertically |                 // otherwise we only display the first two lines | ||||||
|                 ) { |                 maxLines = if (isExpanded) Int.MAX_VALUE else 2, | ||||||
|                     Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { |                 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( |                         Image( | ||||||
|                             painter = painterResource(R.drawable.ic_thumb_up), |                             painter = painterResource(R.drawable.ic_heart), | ||||||
|                             contentDescription = stringResource(R.string.detail_likes_img_view_description) |                             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) { |                 if (comment.replies != null) { | ||||||
|                         TextButton(onClick = { showReplies = true }) { |                     TextButton(onClick = { showReplies = true }) { | ||||||
|                             val text = pluralStringResource( |                         val text = pluralStringResource( | ||||||
|                                 R.plurals.replies, comment.replyCount, comment.replyCount.toString() |                             R.plurals.replies, comment.replyCount, comment.replyCount.toString() | ||||||
|                             ) |                         ) | ||||||
|                             Text(text = text) |                         Text(text = text) | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -190,7 +188,7 @@ fun CommentsInfoItem( | |||||||
|     this.replyCount = replyCount |     this.replyCount = replyCount | ||||||
| } | } | ||||||
|  |  | ||||||
| class DescriptionPreviewProvider : PreviewParameterProvider<Description> { | private class DescriptionPreviewProvider : PreviewParameterProvider<Description> { | ||||||
|     override val values = sequenceOf( |     override val values = sequenceOf( | ||||||
|         Description("Hello world!<br><br>This line should be hidden by default.", Description.HTML), |         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), |         Description("Hello world!\n\nThis line should be hidden by default.", Description.PLAIN_TEXT), | ||||||
| @@ -214,6 +212,8 @@ private fun CommentPreview( | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     AppTheme { |     AppTheme { | ||||||
|         Comment(comment) |         Surface(color = MaterialTheme.colorScheme.background) { | ||||||
|  |             Comment(comment) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,18 +1,33 @@ | |||||||
| 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 androidx.compose.foundation.layout.Column | ||||||
| import androidx.compose.foundation.lazy.LazyColumn | import androidx.compose.foundation.lazy.LazyColumn | ||||||
| import androidx.compose.foundation.lazy.rememberLazyListState | import androidx.compose.foundation.lazy.rememberLazyListState | ||||||
| import androidx.compose.material3.HorizontalDivider | 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.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.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.compose.ui.unit.sp | ||||||
|  | import androidx.paging.LoadState | ||||||
|  | import androidx.paging.LoadStates | ||||||
| import androidx.paging.PagingData | import androidx.paging.PagingData | ||||||
| import androidx.paging.compose.collectAsLazyPagingItems | import androidx.paging.compose.collectAsLazyPagingItems | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
| import kotlinx.coroutines.flow.flowOf | import kotlinx.coroutines.flow.flowOf | ||||||
| import my.nanihadesuka.compose.LazyColumnScrollbar | import my.nanihadesuka.compose.LazyColumnScrollbar | ||||||
| import my.nanihadesuka.compose.ScrollbarSettings | import my.nanihadesuka.compose.ScrollbarSettings | ||||||
|  | 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 | ||||||
| @@ -23,38 +38,81 @@ fun CommentSection( | |||||||
|     parentComment: CommentsInfoItem? = null, |     parentComment: CommentsInfoItem? = null, | ||||||
| ) { | ) { | ||||||
|     val replies = flow.collectAsLazyPagingItems() |     val replies = flow.collectAsLazyPagingItems() | ||||||
|     val listState = rememberLazyListState() |     val itemCount by remember { derivedStateOf { replies.itemCount } } | ||||||
|  |  | ||||||
|     LazyColumnScrollbar(state = listState, settings = ScrollbarSettings.Default) { |     Surface(color = MaterialTheme.colorScheme.background) { | ||||||
|         LazyColumn(state = listState) { |         val refresh = replies.loadState.refresh | ||||||
|             if (parentComment != null) { |         if (itemCount == 0 && refresh !is LoadState.Loading) { | ||||||
|                 item { |             NoCommentsMessage((refresh as? LoadState.Error)?.error) | ||||||
|                     CommentRepliesHeader(comment = parentComment) |         } else { | ||||||
|                     HorizontalDivider(thickness = 1.dp) |             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 = "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 CommentSectionPreview() { | private fun CommentSectionPreview( | ||||||
|     val comments = (1..100).map { |     @PreviewParameter(CommentDataProvider::class) pagingData: PagingData<CommentsInfoItem> | ||||||
|         CommentsInfoItem( | ) { | ||||||
|             commentText = Description("Comment $it", Description.PLAIN_TEXT), |  | ||||||
|             uploaderName = "Test" |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
|     val flow = flowOf(PagingData.from(comments)) |  | ||||||
|  |  | ||||||
|     AppTheme { |     AppTheme { | ||||||
|         CommentSection(flow = flow) |         CommentSection(flow = flowOf(pagingData)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class CommentsSource( | |||||||
|             .subscribeOn(Schedulers.io()) |             .subscribeOn(Schedulers.io()) | ||||||
|             .map { |             .map { | ||||||
|                 if (it.isCommentsDisabled) { |                 if (it.isCommentsDisabled) { | ||||||
|                     LoadResult.Invalid() |                     LoadResult.Error(CommentsDisabledException()) | ||||||
|                 } else { |                 } else { | ||||||
|                     LoadResult.Page(it.relatedItems, null, it.nextPage) |                     LoadResult.Page(it.relatedItems, null, it.nextPage) | ||||||
|                 } |                 } | ||||||
| @@ -34,3 +34,5 @@ class CommentsSource( | |||||||
|  |  | ||||||
|     override fun getRefreshKey(state: PagingState<Page, CommentsInfoItem>) = null |     override fun getRefreshKey(state: PagingState<Page, CommentsInfoItem>) = null | ||||||
| } | } | ||||||
|  |  | ||||||
|  | class CommentsDisabledException : RuntimeException() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Isira Seneviratne
					Isira Seneviratne