Skip to content

Commit 3cb3889

Browse files
committed
Add separate live chat tab alongside comments tab
1 parent e283ab2 commit 3cb3889

9 files changed

Lines changed: 252 additions & 132 deletions

File tree

app/src/main/java/org/schabi/newpipe/fragments/detail/TabAdapter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public void addFragment(final Fragment fragment, final String title) {
4040
mFragmentTitleList.add(title);
4141
}
4242

43+
public void addFragment(final Fragment fragment, final String title, final int position) {
44+
mFragmentList.add(position, fragment);
45+
mFragmentTitleList.add(position, title);
46+
}
47+
4348
public void clearAllItems() {
4449
mFragmentList.clear();
4550
mFragmentTitleList.clear();

app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.kt

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ import org.schabi.newpipe.fragments.BaseStateFragment
8585
import org.schabi.newpipe.fragments.EmptyFragment
8686
import org.schabi.newpipe.fragments.MainFragment
8787
import org.schabi.newpipe.fragments.list.comments.CommentsFragment.Companion.getInstance
88+
import org.schabi.newpipe.fragments.list.comments.LiveChatFragment
8889
import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment.Companion.getInstance
8990
import org.schabi.newpipe.ktx.AnimationType
9091
import org.schabi.newpipe.ktx.animate
@@ -323,7 +324,10 @@ class VideoDetailFragment :
323324
if (tabSettingsChanged) {
324325
tabSettingsChanged = false
325326
initTabs()
326-
currentInfo?.let { updateTabs(it) }
327+
currentInfo?.let {
328+
updateTabs(it)
329+
addLiveChatTabIfNeeded(it)
330+
}
327331
}
328332

329333
// Check if it was loading when the fragment was stopped/paused
@@ -912,6 +916,33 @@ class VideoDetailFragment :
912916
}
913917
}
914918

919+
private fun addLiveChatTabIfNeeded(streamInfo: StreamInfo) {
920+
if (!streamInfo.hasLiveChat()) {
921+
return
922+
}
923+
val continuation = streamInfo.liveChatContinuation ?: return
924+
if (continuation.isEmpty()) {
925+
return
926+
}
927+
if (pageAdapter.getItemPositionByTitle(LIVE_CHAT_TAB_TAG) != -1) {
928+
return
929+
}
930+
931+
val commentsPosition = pageAdapter.getItemPositionByTitle(COMMENTS_TAB_TAG)
932+
val insertPosition = if (commentsPosition >= 0) commentsPosition + 1 else pageAdapter.count
933+
934+
pageAdapter.addFragment(
935+
LiveChatFragment.getInstance(serviceId, url, continuation),
936+
LIVE_CHAT_TAB_TAG,
937+
insertPosition
938+
)
939+
tabIcons.add(insertPosition, R.drawable.ic_live_tv)
940+
tabContentDescriptions.add(insertPosition, R.string.live_chat_tab_description)
941+
pageAdapter.notifyDataSetUpdate()
942+
updateTabIconsAndContentDescriptions()
943+
updateTabLayoutVisibility()
944+
}
945+
915946
fun updateTabLayoutVisibility() {
916947
if (nullableBinding == null) {
917948
// If binding is null we do not need to and should not do anything with its object(s)
@@ -1418,6 +1449,7 @@ class VideoDetailFragment :
14181449
setInitialData(info.serviceId, info.originalUrl, info.name, playQueue)
14191450

14201451
updateTabs(info)
1452+
addLiveChatTabIfNeeded(info)
14211453

14221454
binding.detailThumbnailPlayButton.animate(true, 200)
14231455
binding.detailVideoTitleView.text = title
@@ -2342,6 +2374,7 @@ class VideoDetailFragment :
23422374
App.PACKAGE_NAME + ".VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED"
23432375

23442376
private const val COMMENTS_TAB_TAG = "COMMENTS"
2377+
private const val LIVE_CHAT_TAB_TAG = "LIVE_CHAT"
23452378
private const val RELATED_TAB_TAG = "NEXT VIDEO"
23462379
private const val DESCRIPTION_TAB_TAG = "DESCRIPTION TAB"
23472380
private const val EMPTY_TAB_TAG = "EMPTY TAB"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.schabi.newpipe.fragments.list.comments
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.ViewGroup
6+
import androidx.compose.material3.Surface
7+
import androidx.core.os.bundleOf
8+
import androidx.fragment.app.Fragment
9+
import androidx.fragment.compose.content
10+
import org.schabi.newpipe.ui.components.video.comment.LiveChatSection
11+
import org.schabi.newpipe.ui.theme.AppTheme
12+
import org.schabi.newpipe.util.KEY_SERVICE_ID
13+
import org.schabi.newpipe.util.KEY_URL
14+
import org.schabi.newpipe.viewmodels.LiveChatViewModel
15+
16+
class LiveChatFragment : Fragment() {
17+
override fun onCreateView(
18+
inflater: LayoutInflater,
19+
container: ViewGroup?,
20+
savedInstanceState: Bundle?
21+
) = content {
22+
AppTheme {
23+
Surface {
24+
LiveChatSection()
25+
}
26+
}
27+
}
28+
29+
companion object {
30+
@JvmStatic
31+
fun getInstance(serviceId: Int, url: String?, liveChatContinuation: String) = LiveChatFragment().apply {
32+
arguments = bundleOf(
33+
KEY_SERVICE_ID to serviceId,
34+
KEY_URL to url,
35+
LiveChatViewModel.KEY_LIVE_CHAT_CONTINUATION to liveChatContinuation
36+
)
37+
}
38+
}
39+
}

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,22 @@ package org.schabi.newpipe.ui.components.video.comment
33
import androidx.compose.runtime.Immutable
44
import org.schabi.newpipe.extractor.Page
55
import org.schabi.newpipe.extractor.comments.CommentsInfo
6-
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
76

87
@Immutable
98
class CommentInfo(
109
val serviceId: Int,
1110
val url: String,
12-
val comments: List<CommentsInfoItem>,
11+
val comments: List<org.schabi.newpipe.extractor.comments.CommentsInfoItem>,
1312
val nextPage: Page?,
1413
val commentCount: Int,
15-
val isCommentsDisabled: Boolean,
16-
val isLiveChat: Boolean
14+
val isCommentsDisabled: Boolean
1715
) {
1816
constructor(commentsInfo: CommentsInfo) : this(
1917
commentsInfo.serviceId,
2018
commentsInfo.url,
2119
commentsInfo.relatedItems,
2220
commentsInfo.nextPage,
2321
commentsInfo.commentsCount,
24-
commentsInfo.isCommentsDisabled,
25-
commentsInfo.isLiveChat
22+
commentsInfo.isCommentsDisabled
2623
)
2724
}

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

Lines changed: 41 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import androidx.compose.material3.MaterialTheme
1212
import androidx.compose.material3.Surface
1313
import androidx.compose.material3.Text
1414
import androidx.compose.runtime.Composable
15-
import androidx.compose.runtime.LaunchedEffect
1615
import androidx.compose.runtime.getValue
1716
import androidx.compose.ui.Alignment
1817
import androidx.compose.ui.Modifier
@@ -31,7 +30,6 @@ import kotlinx.coroutines.flow.flowOf
3130
import org.schabi.newpipe.R
3231
import org.schabi.newpipe.error.ErrorInfo
3332
import org.schabi.newpipe.error.UserAction
34-
import org.schabi.newpipe.extractor.Page
3533
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
3634
import org.schabi.newpipe.extractor.stream.Description
3735
import org.schabi.newpipe.ui.components.common.ErrorPanel
@@ -46,28 +44,18 @@ import org.schabi.newpipe.viewmodels.util.Resource
4644
@Composable
4745
fun CommentSection(commentsViewModel: CommentsViewModel = viewModel()) {
4846
val state by commentsViewModel.uiState.collectAsStateWithLifecycle()
49-
val liveChatItems by commentsViewModel.liveChatItems.collectAsStateWithLifecycle()
50-
CommentSection(state, commentsViewModel.comments, liveChatItems)
47+
CommentSection(state, commentsViewModel.comments)
5148
}
5249

5350
@Composable
5451
private fun CommentSection(
5552
uiState: Resource<CommentInfo>,
56-
commentsFlow: Flow<PagingData<CommentsInfoItem>>,
57-
liveChatItems: List<CommentsInfoItem>
53+
commentsFlow: Flow<PagingData<CommentsInfoItem>>
5854
) {
5955
val comments = commentsFlow.collectAsLazyPagingItems()
6056
val nestedScrollInterop = rememberNestedScrollInteropConnection()
6157
val state = rememberLazyListState()
6258

63-
// Auto-scroll to top when new live chat messages arrive
64-
val isLiveChat = uiState is Resource.Success && uiState.data.isLiveChat
65-
LaunchedEffect(liveChatItems.size) {
66-
if (isLiveChat && liveChatItems.isNotEmpty()) {
67-
state.scrollToItem(0)
68-
}
69-
}
70-
7159
LazyColumnThemedScrollbar(state = state) {
7260
LazyColumn(
7361
modifier = Modifier
@@ -86,17 +74,16 @@ private fun CommentSection(
8674
val commentInfo = uiState.data
8775
val count = commentInfo.commentCount
8876

89-
if (commentInfo.isCommentsDisabled && !commentInfo.isLiveChat) {
77+
if (commentInfo.isCommentsDisabled) {
9078
item {
9179
EmptyStateComposable(
9280
spec = EmptyStateSpec.DisabledComments,
9381
modifier = Modifier
9482
.fillMaxWidth()
9583
.heightIn(min = 128.dp)
96-
9784
)
9885
}
99-
} else if (count == 0 && !commentInfo.isLiveChat) {
86+
} else if (count == 0) {
10087
item {
10188
EmptyStateComposable(
10289
spec = EmptyStateSpec.NoComments,
@@ -106,8 +93,7 @@ private fun CommentSection(
10693
)
10794
}
10895
} else {
109-
// Show title for regular comments, but not for live chat
110-
if (count >= 0 && !commentInfo.isLiveChat) {
96+
if (count >= 0) {
11197
item {
11298
Text(
11399
modifier = Modifier
@@ -119,66 +105,47 @@ private fun CommentSection(
119105
}
120106
}
121107

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

147-
is LoadState.Error -> {
148-
val errorInfo = ErrorInfo(
149-
throwable = refresh.error,
150-
userAction = UserAction.REQUESTED_COMMENTS,
151-
request = "comments"
152-
)
153-
136+
else -> {
137+
if (comments.itemCount == 0) {
154138
item {
155-
Box(
139+
EmptyStateComposable(
140+
spec = EmptyStateSpec.NoComments,
156141
modifier = Modifier
157142
.fillMaxWidth()
158-
) {
159-
ErrorPanel(
160-
errorInfo = errorInfo,
161-
onRetry = { comments.retry() },
162-
modifier = Modifier.align(Alignment.Center)
163-
)
164-
}
143+
.heightIn(min = 128.dp)
144+
)
165145
}
166-
}
167-
168-
else -> {
169-
if (comments.itemCount == 0) {
170-
item {
171-
EmptyStateComposable(
172-
spec = EmptyStateSpec.NoComments,
173-
modifier = Modifier
174-
.fillMaxWidth()
175-
.heightIn(min = 128.dp)
176-
)
177-
}
178-
} else {
179-
items(comments.itemCount) {
180-
Comment(comment = comments[it]!!) {}
181-
}
146+
} else {
147+
items(comments.itemCount) {
148+
Comment(comment = comments[it]!!) {}
182149
}
183150
}
184151
}
@@ -217,7 +184,7 @@ private fun CommentSection(
217184
private fun CommentSectionLoadingPreview() {
218185
AppTheme {
219186
Surface {
220-
CommentSection(uiState = Resource.Loading, commentsFlow = flowOf(), liveChatItems = emptyList())
187+
CommentSection(uiState = Resource.Loading, commentsFlow = flowOf())
221188
}
222189
}
223190
}
@@ -233,7 +200,7 @@ private fun CommentSectionSuccessPreview() {
233200
Description.PLAIN_TEXT
234201
),
235202
uploaderName = "Test",
236-
replies = Page(""),
203+
replies = org.schabi.newpipe.extractor.Page(""),
237204
replyCount = 10
238205
)
239206
) + (2..10).map {
@@ -253,12 +220,10 @@ private fun CommentSectionSuccessPreview() {
253220
comments = comments,
254221
nextPage = null,
255222
commentCount = 10,
256-
isCommentsDisabled = false,
257-
isLiveChat = false
223+
isCommentsDisabled = false
258224
)
259225
),
260-
commentsFlow = flowOf(PagingData.from(comments)),
261-
liveChatItems = emptyList()
226+
commentsFlow = flowOf(PagingData.from(comments))
262227
)
263228
}
264229
}
@@ -270,7 +235,7 @@ private fun CommentSectionSuccessPreview() {
270235
private fun CommentSectionErrorPreview() {
271236
AppTheme {
272237
Surface {
273-
CommentSection(uiState = Resource.Error(RuntimeException()), commentsFlow = flowOf(), liveChatItems = emptyList())
238+
CommentSection(uiState = Resource.Error(RuntimeException()), commentsFlow = flowOf())
274239
}
275240
}
276241
}

0 commit comments

Comments
 (0)