Skip to content

Commit 7727e97

Browse files
committed
Fix live chat to use direct list instead of PagingData
1 parent 3193427 commit 7727e97

2 files changed

Lines changed: 99 additions & 63 deletions

File tree

app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ import org.schabi.newpipe.viewmodels.util.Resource
4545
@Composable
4646
fun CommentSection(commentsViewModel: CommentsViewModel = viewModel()) {
4747
val state by commentsViewModel.uiState.collectAsStateWithLifecycle()
48-
CommentSection(state, commentsViewModel.comments)
48+
val liveChatItems by commentsViewModel.liveChatItems.collectAsStateWithLifecycle()
49+
CommentSection(state, commentsViewModel.comments, liveChatItems)
4950
}
5051

5152
@Composable
5253
private fun CommentSection(
5354
uiState: Resource<CommentInfo>,
54-
commentsFlow: Flow<PagingData<CommentsInfoItem>>
55+
commentsFlow: Flow<PagingData<CommentsInfoItem>>,
56+
liveChatItems: List<CommentsInfoItem>
5557
) {
5658
val comments = commentsFlow.collectAsLazyPagingItems()
5759
val nestedScrollInterop = rememberNestedScrollInteropConnection()
@@ -75,7 +77,7 @@ private fun CommentSection(
7577
val commentInfo = uiState.data
7678
val count = commentInfo.commentCount
7779

78-
if (commentInfo.isCommentsDisabled && !commentInfo.isLiveChat) {
80+
if (commentInfo.isCommentsDisabled) {
7981
item {
8082
EmptyStateComposable(
8183
spec = EmptyStateSpec.DisabledComments,
@@ -85,7 +87,7 @@ private fun CommentSection(
8587

8688
)
8789
}
88-
} else if (count == 0 && !commentInfo.isLiveChat) {
90+
} else if (count == 0) {
8991
item {
9092
EmptyStateComposable(
9193
spec = EmptyStateSpec.NoComments,
@@ -95,7 +97,7 @@ private fun CommentSection(
9597
)
9698
}
9799
} else {
98-
// do not show anything if the comment count is unknown
100+
// Show title for regular comments, but not for live chat
99101
if (count >= 0 && !commentInfo.isLiveChat) {
100102
item {
101103
Text(
@@ -107,47 +109,67 @@ private fun CommentSection(
107109
)
108110
}
109111
}
110-
when (val refresh = comments.loadState.refresh) {
111-
is LoadState.Loading -> {
112-
item {
113-
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
114-
}
115-
}
116-
117-
is LoadState.Error -> {
118-
val errorInfo = ErrorInfo(
119-
throwable = refresh.error,
120-
userAction = UserAction.REQUESTED_COMMENTS,
121-
request = "comments"
122-
)
123112

113+
if (commentInfo.isLiveChat) {
114+
// Live chat: render items directly without Paging 3
115+
if (liveChatItems.isEmpty()) {
124116
item {
125-
Box(
117+
EmptyStateComposable(
118+
spec = EmptyStateSpec.NoComments,
126119
modifier = Modifier
127120
.fillMaxWidth()
128-
) {
129-
ErrorPanel(
130-
errorInfo = errorInfo,
131-
onRetry = { comments.retry() },
132-
modifier = Modifier.align(Alignment.Center)
133-
)
134-
}
121+
.heightIn(min = 128.dp)
122+
)
123+
}
124+
} else {
125+
items(liveChatItems.size, key = { liveChatItems[it].commentId }) {
126+
Comment(comment = liveChatItems[it]) {}
135127
}
136128
}
129+
} else {
130+
// Normal comments via Paging 3
131+
when (val refresh = comments.loadState.refresh) {
132+
is LoadState.Loading -> {
133+
item {
134+
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
135+
}
136+
}
137+
138+
is LoadState.Error -> {
139+
val errorInfo = ErrorInfo(
140+
throwable = refresh.error,
141+
userAction = UserAction.REQUESTED_COMMENTS,
142+
request = "comments"
143+
)
137144

138-
else -> {
139-
if (comments.itemCount == 0 && commentInfo.isLiveChat) {
140145
item {
141-
EmptyStateComposable(
142-
spec = EmptyStateSpec.NoComments,
146+
Box(
143147
modifier = Modifier
144148
.fillMaxWidth()
145-
.heightIn(min = 128.dp)
146-
)
149+
) {
150+
ErrorPanel(
151+
errorInfo = errorInfo,
152+
onRetry = { comments.retry() },
153+
modifier = Modifier.align(Alignment.Center)
154+
)
155+
}
147156
}
148-
} else {
149-
items(comments.itemCount) {
150-
Comment(comment = comments[it]!!) {}
157+
}
158+
159+
else -> {
160+
if (comments.itemCount == 0) {
161+
item {
162+
EmptyStateComposable(
163+
spec = EmptyStateSpec.NoComments,
164+
modifier = Modifier
165+
.fillMaxWidth()
166+
.heightIn(min = 128.dp)
167+
)
168+
}
169+
} else {
170+
items(comments.itemCount) {
171+
Comment(comment = comments[it]!!) {}
172+
}
151173
}
152174
}
153175
}
@@ -186,7 +208,7 @@ private fun CommentSection(
186208
private fun CommentSectionLoadingPreview() {
187209
AppTheme {
188210
Surface {
189-
CommentSection(uiState = Resource.Loading, commentsFlow = flowOf())
211+
CommentSection(uiState = Resource.Loading, commentsFlow = flowOf(), liveChatItems = emptyList())
190212
}
191213
}
192214
}
@@ -226,7 +248,8 @@ private fun CommentSectionSuccessPreview() {
226248
isLiveChat = false
227249
)
228250
),
229-
commentsFlow = flowOf(PagingData.from(comments))
251+
commentsFlow = flowOf(PagingData.from(comments)),
252+
liveChatItems = emptyList()
230253
)
231254
}
232255
}
@@ -238,7 +261,7 @@ private fun CommentSectionSuccessPreview() {
238261
private fun CommentSectionErrorPreview() {
239262
AppTheme {
240263
Surface {
241-
CommentSection(uiState = Resource.Error(RuntimeException()), commentsFlow = flowOf())
264+
CommentSection(uiState = Resource.Error(RuntimeException()), commentsFlow = flowOf(), liveChatItems = emptyList())
242265
}
243266
}
244267
}

app/src/main/java/org/schabi/newpipe/viewmodels/CommentsViewModel.kt

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ import kotlinx.coroutines.Dispatchers
1212
import kotlinx.coroutines.ExperimentalCoroutinesApi
1313
import kotlinx.coroutines.delay
1414
import kotlinx.coroutines.flow.Flow
15+
import kotlinx.coroutines.flow.MutableStateFlow
1516
import kotlinx.coroutines.flow.SharingStarted
17+
import kotlinx.coroutines.flow.StateFlow
1618
import kotlinx.coroutines.flow.filterIsInstance
1719
import kotlinx.coroutines.flow.flatMapLatest
18-
import kotlinx.coroutines.flow.flow
20+
import kotlinx.coroutines.flow.flowOf
1921
import kotlinx.coroutines.flow.flowOn
2022
import kotlinx.coroutines.flow.map
2123
import kotlinx.coroutines.flow.stateIn
24+
import kotlinx.coroutines.isActive
25+
import kotlinx.coroutines.launch
2226
import org.schabi.newpipe.extractor.NewPipe
2327
import org.schabi.newpipe.extractor.comments.CommentsInfo
2428
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
@@ -51,15 +55,21 @@ class CommentsViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
5155
.flowOn(Dispatchers.IO)
5256
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), Resource.Loading)
5357

54-
// Live chat via PagingData (broken approach)
58+
// Separate flow for live chat items (not using Paging 3)
59+
private val _liveChatItems = MutableStateFlow<List<CommentsInfoItem>>(emptyList())
60+
val liveChatItems: StateFlow<List<CommentsInfoItem>> = _liveChatItems
61+
5562
@OptIn(ExperimentalCoroutinesApi::class)
5663
val comments: Flow<PagingData<CommentsInfoItem>> = uiState
5764
.filterIsInstance<Resource.Success<CommentInfo>>()
5865
.flatMapLatest {
5966
val info = it.data
6067
Log.i(TAG, "flatMapLatest: isLiveChat=${info.isLiveChat}, items=${info.comments.size}")
6168
if (info.isLiveChat) {
62-
liveChatPagingData(info)
69+
_liveChatItems.value = info.comments
70+
startLiveChatPolling(info)
71+
// Return empty PagingData for live chat (items come from liveChatItems flow)
72+
kotlinx.coroutines.flow.flowOf(androidx.paging.PagingData.empty())
6373
} else {
6474
Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) {
6575
CommentsSource(info)
@@ -68,29 +78,32 @@ class CommentsViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
6878
}
6979
.cachedIn(viewModelScope)
7080

71-
private fun liveChatPagingData(info: CommentInfo): Flow<PagingData<CommentsInfoItem>> = flow {
72-
val allItems = info.comments.toMutableList()
73-
emit(PagingData.from(allItems))
81+
private fun startLiveChatPolling(info: CommentInfo) {
7482
var nextPage = info.nextPage
75-
while (true) {
76-
delay(3000)
77-
if (nextPage == null) {
78-
Log.d(TAG, "liveChatPolling: nextPage is null, skipping")
79-
continue
80-
}
81-
try {
82-
Log.d(TAG, "liveChatPolling: fetching more items...")
83-
val result = CommentsInfo.getMoreItems(
84-
NewPipe.getService(info.serviceId),
85-
info.url,
86-
nextPage
87-
)
88-
Log.i(TAG, "liveChatPolling: fetched ${result.items.size} items")
89-
allItems.addAll(result.items)
90-
emit(PagingData.from(allItems))
91-
nextPage = result.nextPage
92-
} catch (e: Exception) {
93-
Log.e(TAG, "liveChatPolling: failed to fetch more items", e)
83+
Log.i(TAG, "startLiveChatPolling() items=${info.comments.size}, nextPage=${nextPage != null}")
84+
85+
viewModelScope.launch(Dispatchers.IO) {
86+
while (isActive) {
87+
delay(3000)
88+
if (nextPage == null) {
89+
Log.d(TAG, "liveChatPolling: nextPage is null, skipping")
90+
continue
91+
}
92+
try {
93+
Log.d(TAG, "liveChatPolling: fetching more items...")
94+
val result = CommentsInfo.getMoreItems(
95+
NewPipe.getService(info.serviceId),
96+
info.url,
97+
nextPage
98+
)
99+
Log.i(TAG, "liveChatPolling: fetched ${result.items.size} items, nextPage=${result.nextPage != null}")
100+
if (result.items.isNotEmpty()) {
101+
_liveChatItems.value = _liveChatItems.value + result.items
102+
}
103+
nextPage = result.nextPage
104+
} catch (e: Exception) {
105+
Log.e(TAG, "liveChatPolling: failed to fetch more items", e)
106+
}
94107
}
95108
}
96109
}

0 commit comments

Comments
 (0)