diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index a364c42cd..8fcf1e663 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -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); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/Comment.kt b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/Comment.kt index 704b5d7f7..abc8bfafe 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/Comment.kt +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/Comment.kt @@ -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 { +private class DescriptionPreviewProvider : PreviewParameterProvider { override val values = sequenceOf( Description("Hello world!

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) + } } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentSection.kt b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentSection.kt index ed1969b68..900392c09 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentSection.kt +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentSection.kt @@ -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> { + 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(), + LoadStates(LoadState.Error(CommentsDisabledException()), notLoading, notLoading) + ), + // No comments + PagingData.from( + listOf(), + 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 +) { AppTheme { - CommentSection(flow = flow) + CommentSection(flow = flowOf(pagingData)) } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsSource.kt b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsSource.kt index 6288efaec..9e46a9df1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsSource.kt +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsSource.kt @@ -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) = null } + +class CommentsDisabledException : RuntimeException()