From 823d4a041f59abf53242097e8bff58af7b849faa Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 30 Aug 2024 16:59:15 +0530 Subject: [PATCH] Improve loading indicator positioning --- .../list/comments/CommentsFragment.kt | 6 +- .../ui/components/video/comment/Comment.kt | 27 +-- .../video/comment/CommentRepliesDialog.kt | 132 ++++++++++++++ .../video/comment/CommentSection.kt | 165 +++++++----------- 4 files changed, 204 insertions(+), 126 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesDialog.kt diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.kt b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.kt index 2c6898d7a..6e20e1425 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.kt @@ -3,6 +3,8 @@ package org.schabi.newpipe.fragments.list.comments import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.fragment.compose.content @@ -18,7 +20,9 @@ class CommentsFragment : Fragment() { savedInstanceState: Bundle? ) = content { AppTheme { - CommentSection() + Surface(color = MaterialTheme.colorScheme.background) { + CommentSection() + } } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/Comment.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/Comment.kt index d02dc1489..59a451392 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/Comment.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/Comment.kt @@ -15,9 +15,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -25,7 +23,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -42,22 +39,18 @@ 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 androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.cachedIn import coil.compose.AsyncImage import org.schabi.newpipe.R import org.schabi.newpipe.extractor.Page import org.schabi.newpipe.extractor.comments.CommentsInfoItem import org.schabi.newpipe.extractor.stream.Description -import org.schabi.newpipe.paging.CommentsSource import org.schabi.newpipe.ui.components.common.rememberParsedDescription import org.schabi.newpipe.ui.theme.AppTheme import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.image.ImageStrategy -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class) @Composable fun Comment(comment: CommentsInfoItem) { val clipboardManager = LocalClipboardManager.current @@ -157,23 +150,7 @@ fun Comment(comment: CommentsInfoItem) { } if (showReplies) { - ModalBottomSheet(onDismissRequest = { showReplies = false }) { - val coroutineScope = rememberCoroutineScope() - val flow = remember { - Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) { - CommentsSource(comment.serviceId, comment.url, comment.replies) - }.flow - .cachedIn(coroutineScope) - } - - Surface(color = MaterialTheme.colorScheme.background) { - CommentSection( - commentsFlow = flow, - commentCount = comment.replyCount, - parentComment = comment - ) - } - } + CommentRepliesDialog(comment, onDismissRequest = { showReplies = false }) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesDialog.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesDialog.kt new file mode 100644 index 000000000..30131d91e --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesDialog.kt @@ -0,0 +1,132 @@ +package org.schabi.newpipe.ui.components.video.comment + +import android.content.res.Configuration +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.paging.LoadState +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +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.paging.CommentsSource +import org.schabi.newpipe.ui.components.common.LoadingIndicator +import org.schabi.newpipe.ui.components.common.NoItemsMessage +import org.schabi.newpipe.ui.theme.AppTheme +import org.schabi.newpipe.ui.theme.md_theme_dark_primary + +@Composable +fun CommentRepliesDialog( + parentComment: CommentsInfoItem, + onDismissRequest: () -> Unit, +) { + val coroutineScope = rememberCoroutineScope() + val commentsFlow = remember { + Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) { + CommentsSource(parentComment.serviceId, parentComment.url, parentComment.replies) + }.flow + .cachedIn(coroutineScope) + } + + CommentRepliesDialog(parentComment, commentsFlow, onDismissRequest) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun CommentRepliesDialog( + parentComment: CommentsInfoItem, + commentsFlow: Flow>, + onDismissRequest: () -> Unit, +) { + val comments = commentsFlow.collectAsLazyPagingItems() + val nestedScrollInterop = rememberNestedScrollInteropConnection() + val state = rememberLazyListState() + + ModalBottomSheet(onDismissRequest = onDismissRequest) { + Surface(color = MaterialTheme.colorScheme.background) { + LazyColumnScrollbar( + state = state, + settings = ScrollbarSettings.Default.copy( + thumbSelectedColor = md_theme_dark_primary, + thumbUnselectedColor = Color.Red + ) + ) { + LazyColumn( + modifier = Modifier.nestedScroll(nestedScrollInterop), + state = state + ) { + item { + CommentRepliesHeader(comment = parentComment) + HorizontalDivider(thickness = 1.dp) + } + + if (comments.itemCount == 0) { + item { + val refresh = comments.loadState.refresh + if (refresh is LoadState.Loading) { + LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) + } else { + val message = if (refresh is LoadState.Error) { + R.string.error_unable_to_load_comments + } else { + R.string.no_comments + } + NoItemsMessage(message) + } + } + } else { + items(comments.itemCount) { + Comment(comment = comments[it]!!) + } + } + } + } + } + } +} + +@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun CommentRepliesDialogPreview() { + val comment = CommentsInfoItem( + commentText = Description("Hello world!", Description.PLAIN_TEXT), + uploaderName = "Test", + likeCount = 100, + isPinned = true, + isHeartedByUploader = true + ) + val replies = (1..10).map { + CommentsInfoItem( + commentText = Description("Reply $it", Description.PLAIN_TEXT), + uploaderName = "Test" + ) + } + val flow = flowOf(PagingData.from(replies)) + + AppTheme { + CommentRepliesDialog(comment, flow, onDismissRequest = {}) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt index bdd72cea5..f643af69d 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt @@ -4,7 +4,6 @@ import android.content.res.Configuration import androidx.compose.foundation.layout.padding 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 @@ -40,94 +39,86 @@ import org.schabi.newpipe.viewmodels.util.Resource @Composable fun CommentSection(commentsViewModel: CommentsViewModel = viewModel()) { - Surface(color = MaterialTheme.colorScheme.background) { - val state by commentsViewModel.uiState.collectAsStateWithLifecycle() - CommentSection(state, commentsViewModel.comments) - } + val state by commentsViewModel.uiState.collectAsStateWithLifecycle() + CommentSection(state, commentsViewModel.comments) } @Composable private fun CommentSection( uiState: Resource, commentsFlow: Flow> -) { - when (uiState) { - is Resource.Loading -> LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) - - is Resource.Success -> { - val commentsInfo = uiState.data - CommentSection( - commentsFlow = commentsFlow, - commentCount = commentsInfo.commentCount, - isCommentsDisabled = commentsInfo.isCommentsDisabled - ) - } - - is Resource.Error -> { - // This is not rendered as VideoDetailFragment handles errors - } - } -} - -@Composable -fun CommentSection( - commentsFlow: Flow>, - commentCount: Int, - parentComment: CommentsInfoItem? = null, - isCommentsDisabled: Boolean = false, ) { val comments = commentsFlow.collectAsLazyPagingItems() val nestedScrollInterop = rememberNestedScrollInteropConnection() val state = rememberLazyListState() - LazyColumnScrollbar( - state = state, - settings = ScrollbarSettings.Default.copy( - thumbSelectedColor = md_theme_dark_primary, - thumbUnselectedColor = Color.Red - ) - ) { - LazyColumn( - modifier = Modifier.nestedScroll(nestedScrollInterop), - state = state + Surface(color = MaterialTheme.colorScheme.background) { + LazyColumnScrollbar( + state = state, + settings = ScrollbarSettings.Default.copy( + thumbSelectedColor = md_theme_dark_primary, + thumbUnselectedColor = Color.Red + ) ) { - if (parentComment != null) { - item { - CommentRepliesHeader(comment = parentComment) - HorizontalDivider(thickness = 1.dp) - } - } - - if (comments.itemCount == 0) { - item { - val refresh = comments.loadState.refresh - if (refresh is LoadState.Loading) { - LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) - } else { - val message = if (refresh is LoadState.Error) { - R.string.error_unable_to_load_comments - } else if (isCommentsDisabled) { - R.string.comments_are_disabled - } else { - R.string.no_comments + LazyColumn( + modifier = Modifier.nestedScroll(nestedScrollInterop), + state = state + ) { + when (uiState) { + is Resource.Loading -> { + item { + LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) } - NoItemsMessage(message) } - } - } else { - // The number of replies is already shown in the main comment section - if (parentComment == null) { - item { - Text( - modifier = Modifier.padding(start = 8.dp), - text = pluralStringResource(R.plurals.comments, commentCount, commentCount), - fontWeight = FontWeight.Bold - ) - } - } - items(comments.itemCount) { - Comment(comment = comments[it]!!) + is Resource.Success -> { + val commentInfo = uiState.data + val count = commentInfo.commentCount + + if (commentInfo.isCommentsDisabled) { + item { + NoItemsMessage(R.string.comments_are_disabled) + } + } else if (count == 0) { + item { + NoItemsMessage(R.string.no_comments) + } + } else { + item { + Text( + modifier = Modifier.padding(start = 8.dp), + text = pluralStringResource(R.plurals.comments, count, count), + fontWeight = FontWeight.Bold + ) + } + + when (comments.loadState.refresh) { + is LoadState.Loading -> { + item { + LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) + } + } + + is LoadState.Error -> { + item { + NoItemsMessage(R.string.error_unable_to_load_comments) + } + } + + else -> { + items(comments.itemCount) { + Comment(comment = comments[it]!!) + } + } + } + } + } + + is Resource.Error -> { + item { + NoItemsMessage(R.string.error_unable_to_load_comments) + } + } } } } @@ -191,29 +182,3 @@ private fun CommentSectionErrorPreview() { } } } - -@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun CommentRepliesPreview() { - val comment = CommentsInfoItem( - commentText = Description("Hello world!", Description.PLAIN_TEXT), - uploaderName = "Test", - likeCount = 100, - isPinned = true, - isHeartedByUploader = true - ) - val replies = (1..10).map { - CommentsInfoItem( - commentText = Description("Reply $it", Description.PLAIN_TEXT), - uploaderName = "Test" - ) - } - val flow = flowOf(PagingData.from(replies)) - - AppTheme { - Surface(color = MaterialTheme.colorScheme.background) { - CommentSection(parentComment = comment, commentsFlow = flow, commentCount = 10) - } - } -}