mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-25 04:22:30 +01:00
Improve loading indicator positioning
This commit is contained in:
parent
62d4044d6c
commit
823d4a041f
@ -3,6 +3,8 @@ package org.schabi.newpipe.fragments.list.comments
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.compose.content
|
import androidx.fragment.compose.content
|
||||||
@ -18,7 +20,9 @@ class CommentsFragment : Fragment() {
|
|||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
) = content {
|
) = content {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
CommentSection()
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
|
CommentSection()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -25,7 +23,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
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.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.paging.Pager
|
|
||||||
import androidx.paging.PagingConfig
|
|
||||||
import androidx.paging.cachedIn
|
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.extractor.Page
|
import org.schabi.newpipe.extractor.Page
|
||||||
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.paging.CommentsSource
|
|
||||||
import org.schabi.newpipe.ui.components.common.rememberParsedDescription
|
import org.schabi.newpipe.ui.components.common.rememberParsedDescription
|
||||||
import org.schabi.newpipe.ui.theme.AppTheme
|
import org.schabi.newpipe.ui.theme.AppTheme
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import org.schabi.newpipe.util.NavigationHelper
|
import org.schabi.newpipe.util.NavigationHelper
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy
|
import org.schabi.newpipe.util.image.ImageStrategy
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Comment(comment: CommentsInfoItem) {
|
fun Comment(comment: CommentsInfoItem) {
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
@ -157,23 +150,7 @@ fun Comment(comment: CommentsInfoItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showReplies) {
|
if (showReplies) {
|
||||||
ModalBottomSheet(onDismissRequest = { showReplies = false }) {
|
CommentRepliesDialog(comment, 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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<PagingData<CommentsInfoItem>>,
|
||||||
|
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 = {})
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@ import android.content.res.Configuration
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
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.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -40,94 +39,86 @@ import org.schabi.newpipe.viewmodels.util.Resource
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CommentSection(commentsViewModel: CommentsViewModel = viewModel()) {
|
fun CommentSection(commentsViewModel: CommentsViewModel = viewModel()) {
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
val state by commentsViewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val state by commentsViewModel.uiState.collectAsStateWithLifecycle()
|
CommentSection(state, commentsViewModel.comments)
|
||||||
CommentSection(state, commentsViewModel.comments)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CommentSection(
|
private fun CommentSection(
|
||||||
uiState: Resource<CommentInfo>,
|
uiState: Resource<CommentInfo>,
|
||||||
commentsFlow: Flow<PagingData<CommentsInfoItem>>
|
commentsFlow: Flow<PagingData<CommentsInfoItem>>
|
||||||
) {
|
|
||||||
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<PagingData<CommentsInfoItem>>,
|
|
||||||
commentCount: Int,
|
|
||||||
parentComment: CommentsInfoItem? = null,
|
|
||||||
isCommentsDisabled: Boolean = false,
|
|
||||||
) {
|
) {
|
||||||
val comments = commentsFlow.collectAsLazyPagingItems()
|
val comments = commentsFlow.collectAsLazyPagingItems()
|
||||||
val nestedScrollInterop = rememberNestedScrollInteropConnection()
|
val nestedScrollInterop = rememberNestedScrollInteropConnection()
|
||||||
val state = rememberLazyListState()
|
val state = rememberLazyListState()
|
||||||
|
|
||||||
LazyColumnScrollbar(
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
state = state,
|
LazyColumnScrollbar(
|
||||||
settings = ScrollbarSettings.Default.copy(
|
state = state,
|
||||||
thumbSelectedColor = md_theme_dark_primary,
|
settings = ScrollbarSettings.Default.copy(
|
||||||
thumbUnselectedColor = Color.Red
|
thumbSelectedColor = md_theme_dark_primary,
|
||||||
)
|
thumbUnselectedColor = Color.Red
|
||||||
) {
|
)
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
|
||||||
state = state
|
|
||||||
) {
|
) {
|
||||||
if (parentComment != null) {
|
LazyColumn(
|
||||||
item {
|
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||||
CommentRepliesHeader(comment = parentComment)
|
state = state
|
||||||
HorizontalDivider(thickness = 1.dp)
|
) {
|
||||||
}
|
when (uiState) {
|
||||||
}
|
is Resource.Loading -> {
|
||||||
|
item {
|
||||||
if (comments.itemCount == 0) {
|
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
|
||||||
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
|
|
||||||
}
|
}
|
||||||
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) {
|
is Resource.Success -> {
|
||||||
Comment(comment = comments[it]!!)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user