Skip to content

Commit ff4ec81

Browse files
Revert "perf: remove nest scroll for better perf"
This reverts commit 5bc3b2e.
1 parent 5bc3b2e commit ff4ec81

File tree

7 files changed

+322
-393
lines changed

7 files changed

+322
-393
lines changed

android/src/main/kotlin/project/pipepipe/app/ui/list/CommentList.kt

Lines changed: 90 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
1313
import androidx.compose.foundation.layout.height
1414
import androidx.compose.foundation.layout.padding
1515
import androidx.compose.foundation.lazy.LazyColumn
16-
import androidx.compose.foundation.lazy.LazyListScope
1716
import androidx.compose.foundation.lazy.LazyListState
1817
import androidx.compose.foundation.lazy.items
1918
import androidx.compose.material.icons.Icons
@@ -37,92 +36,113 @@ import project.pipepipe.app.ui.item.CommentItem
3736
import project.pipepipe.app.ui.screens.Screen
3837
import project.pipepipe.app.SharedContext
3938

40-
/**
41-
* LazyListScope extension for comment list content (without the LazyColumn wrapper)
42-
* Used by CommentSection to combine with common header
43-
*/
4439
@OptIn(ExperimentalFoundationApi::class)
45-
fun LazyListScope.commentListContent(
40+
@Composable
41+
fun CommentList(
4642
comments: List<CommentInfo>,
4743
isLoading: Boolean,
4844
hasMoreComments: Boolean,
4945
onShowReplies: (CommentInfo) -> Unit,
5046
onLoadMore: () -> Unit,
5147
showStickyHeader: Boolean = false,
5248
onBackClick: () -> Unit = {},
53-
navController: NavHostController,
54-
onTimestampClick: (Long) -> Unit
49+
modifier: Modifier = Modifier,
50+
listState: LazyListState,
51+
onTimestampClick: (Long) -> Unit,
52+
navController: NavHostController
5553
) {
56-
val uniqueItems = comments.distinctBy { it.url }
54+
val uniqueItems = remember(comments) { comments.distinctBy { it.url } }
5755

58-
if (isLoading && comments.isEmpty()) {
59-
item {
60-
Box(
61-
modifier = Modifier
62-
.fillMaxWidth()
63-
.padding(32.dp),
64-
contentAlignment = Alignment.Center
65-
) {
66-
CircularProgressIndicator()
67-
}
56+
LaunchedEffect(listState, hasMoreComments, isLoading) {
57+
snapshotFlow {
58+
val layoutInfo = listState.layoutInfo
59+
val lastVisibleIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
60+
val totalItemsCount = layoutInfo.totalItemsCount
61+
lastVisibleIndex to totalItemsCount
6862
}
69-
} else {
70-
if (showStickyHeader) {
71-
stickyHeader {
72-
Row(
73-
modifier = Modifier
74-
.fillMaxWidth()
75-
.background(MaterialTheme.colorScheme.surface)
76-
.padding(16.dp),
77-
verticalAlignment = Alignment.CenterVertically,
78-
horizontalArrangement = Arrangement.Start
79-
) {
80-
Icon(
81-
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
82-
contentDescription = "Back",
83-
modifier = Modifier.clickable { onBackClick() }
84-
)
85-
Text(
86-
text = "Replies",
87-
style = MaterialTheme.typography.titleMedium,
88-
modifier = Modifier.padding(start = 16.dp)
89-
)
63+
.distinctUntilChanged()
64+
.collect { (lastVisibleIndex, totalItemsCount) ->
65+
val isAtBottom = totalItemsCount > 0 && lastVisibleIndex == totalItemsCount - 1
66+
if (isAtBottom && hasMoreComments && !isLoading) {
67+
onLoadMore()
9068
}
9169
}
92-
} else {
93-
item { Spacer(modifier = Modifier.height(8.dp)) }
94-
}
70+
}
9571

96-
items(
97-
items = uniqueItems,
98-
key = { it.url!! }
99-
) { comment ->
100-
CommentItem(
101-
commentInfo = comment,
102-
onReplyButtonClick = { onShowReplies(comment) },
103-
onChannelAvatarClick = {
104-
comment.authorUrl?.let {
105-
navController.navigate(Screen.Channel.createRoute(it, comment.serviceId!!))
106-
SharedContext.sharedVideoDetailViewModel.showAsBottomPlayer()
72+
CompositionLocalProvider(LocalOverscrollFactory provides null) {
73+
LazyColumn(
74+
modifier = modifier.fillMaxSize(),
75+
state = listState
76+
) {
77+
if (isLoading && comments.isEmpty()) {
78+
item {
79+
Box(
80+
modifier = Modifier
81+
.fillMaxWidth()
82+
.padding(32.dp),
83+
contentAlignment = Alignment.Center
84+
) {
85+
CircularProgressIndicator()
10786
}
108-
},
109-
onTimestampClick = onTimestampClick,
110-
modifier = Modifier.padding(horizontal = 16.dp)
111-
)
112-
Spacer(modifier = Modifier.height(20.dp))
113-
}
87+
}
88+
} else {
89+
if (showStickyHeader) {
90+
stickyHeader {
91+
Row(
92+
modifier = Modifier
93+
.fillMaxWidth()
94+
.background(MaterialTheme.colorScheme.surface)
95+
.padding(bottom = 16.dp, top = 8.dp),
96+
verticalAlignment = Alignment.CenterVertically,
97+
horizontalArrangement = Arrangement.Start
98+
) {
99+
Icon(
100+
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
101+
contentDescription = "Back",
102+
modifier = Modifier.clickable { onBackClick() }
103+
)
104+
Text(
105+
text = "Replies",
106+
style = MaterialTheme.typography.titleMedium,
107+
modifier = Modifier.padding(start = 16.dp)
108+
)
109+
}
110+
}
111+
} else {
112+
item { Spacer(modifier = Modifier.height(8.dp)) }
113+
}
114+
115+
items(
116+
items = uniqueItems,
117+
key = { it.url!! }
118+
) { comment ->
119+
CommentItem(
120+
commentInfo = comment,
121+
onReplyButtonClick = { onShowReplies(comment) },
122+
onChannelAvatarClick = {
123+
comment.authorUrl?.let {
124+
navController.navigate(Screen.Channel.createRoute(it, comment.serviceId!!))
125+
SharedContext.sharedVideoDetailViewModel.showAsBottomPlayer()
126+
}
127+
},
128+
onTimestampClick = onTimestampClick
129+
)
130+
Spacer(modifier = Modifier.height(20.dp))
131+
}
114132

115-
if (isLoading && comments.isNotEmpty()) {
116-
item {
117-
Box(
118-
modifier = Modifier
119-
.fillMaxWidth()
120-
.padding(16.dp),
121-
contentAlignment = Alignment.Center
122-
) {
123-
CircularProgressIndicator()
133+
if (isLoading && comments.isNotEmpty()) {
134+
item {
135+
Box(
136+
modifier = Modifier
137+
.fillMaxWidth()
138+
.padding(16.dp),
139+
contentAlignment = Alignment.Center
140+
) {
141+
CircularProgressIndicator()
142+
}
143+
}
124144
}
125145
}
126146
}
127147
}
128-
}
148+
}

android/src/main/kotlin/project/pipepipe/app/ui/screens/videodetail/CommentSection.kt

Lines changed: 33 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
11
package project.pipepipe.app.ui.screens.videodetail
22

33
import androidx.activity.compose.BackHandler
4-
import androidx.compose.foundation.ExperimentalFoundationApi
4+
import androidx.compose.foundation.layout.Box
55
import androidx.compose.foundation.layout.fillMaxSize
6-
import androidx.compose.foundation.lazy.LazyColumn
76
import androidx.compose.foundation.lazy.LazyListState
8-
import androidx.compose.runtime.*
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.DisposableEffect
9+
import androidx.compose.runtime.collectAsState
10+
import androidx.compose.runtime.getValue
11+
import androidx.compose.runtime.remember
12+
import androidx.compose.runtime.rememberCoroutineScope
913
import androidx.compose.ui.Modifier
1014
import androidx.navigation.NavHostController
11-
import kotlinx.coroutines.flow.distinctUntilChanged
1215
import kotlinx.coroutines.launch
1316
import project.pipepipe.app.SharedContext
1417
import project.pipepipe.app.ui.component.ErrorComponent
15-
import project.pipepipe.app.ui.list.commentListContent
16-
import project.pipepipe.shared.infoitem.StreamInfo
18+
import project.pipepipe.app.ui.list.CommentList
1719

18-
@OptIn(ExperimentalFoundationApi::class)
1920
@Composable
2021
fun CommentSection(
21-
streamInfo: StreamInfo,
22-
navController: NavHostController,
23-
onPlayAudioClick: () -> Unit,
24-
onAddToPlaylistClick: () -> Unit,
22+
modifier: Modifier = Modifier,
2523
onTimestampClick: (Long) -> Unit,
26-
modifier: Modifier = Modifier
24+
navController: NavHostController,
2725
) {
2826
val viewModel = SharedContext.sharedVideoDetailViewModel
2927
val uiState by viewModel.uiState.collectAsState()
@@ -60,56 +58,24 @@ fun CommentSection(
6058

6159
val serviceId = uiState.currentStreamInfo?.serviceId
6260

63-
if (commentsState.common.error != null) {
64-
ErrorComponent(
65-
error = commentsState.common.error!!,
66-
onRetry = {
67-
uiState.currentStreamInfo?.let { streamInfo ->
68-
coroutineScope.launch {
69-
viewModel.loadComments(streamInfo)
70-
}
71-
}
72-
},
73-
modifier = modifier.fillMaxSize(),
74-
shouldStartFromTop = true
75-
)
76-
} else {
77-
when {
78-
commentsState.parentComment != null -> {
79-
// Replies view with common header
80-
LaunchedEffect(repliesListState, commentsState.replies.nextPageUrl, commentsState.common.isLoading) {
81-
snapshotFlow {
82-
val layoutInfo = repliesListState.layoutInfo
83-
val lastVisibleIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
84-
val totalItemsCount = layoutInfo.totalItemsCount
85-
lastVisibleIndex to totalItemsCount
86-
}
87-
.distinctUntilChanged()
88-
.collect { (lastVisibleIndex, totalItemsCount) ->
89-
val isAtBottom = totalItemsCount > 0 && lastVisibleIndex == totalItemsCount - 1
90-
val hasMoreReplies = commentsState.replies.nextPageUrl != null
91-
if (isAtBottom && hasMoreReplies && !commentsState.common.isLoading) {
92-
serviceId?.let {
93-
viewModel.loadMoreReplies(it)
94-
}
95-
}
61+
Box(modifier = modifier.fillMaxSize()) {
62+
if (commentsState.common.error != null) {
63+
ErrorComponent(
64+
error = commentsState.common.error!!,
65+
onRetry = {
66+
uiState.currentStreamInfo?.let { streamInfo ->
67+
coroutineScope.launch {
68+
viewModel.loadComments(streamInfo)
9669
}
97-
}
98-
99-
LazyColumn(
100-
modifier = modifier.fillMaxSize(),
101-
state = repliesListState
102-
) {
103-
// Common header
104-
videoDetailCommonHeader(
105-
streamInfo = streamInfo,
106-
navController = navController,
107-
onPlayAudioClick = onPlayAudioClick,
108-
onAddToPlaylistClick = onAddToPlaylistClick
109-
)
110-
111-
// Replies content (inline from CommentList)
112-
commentListContent(
70+
}
71+
},
72+
modifier = Modifier.fillMaxSize(),
73+
shouldStartFromTop = true
74+
)
75+
} else {
76+
when {
77+
commentsState.parentComment != null -> {
78+
CommentList(
11379
comments = commentsState.replies.itemList,
11480
isLoading = commentsState.common.isLoading,
11581
hasMoreComments = commentsState.replies.nextPageUrl != null,
@@ -123,49 +89,16 @@ fun CommentSection(
12389
},
12490
showStickyHeader = true,
12591
onBackClick = { viewModel.backToCommentList() },
92+
listState = repliesListState,
12693
navController = navController,
12794
onTimestampClick = onTimestampClick
12895
)
129-
}
130-
BackHandler {
131-
viewModel.backToCommentList()
132-
}
133-
}
134-
else -> {
135-
// Comments list with common header
136-
LaunchedEffect(commentsListState, commentsState.comments.nextPageUrl, commentsState.common.isLoading) {
137-
snapshotFlow {
138-
val layoutInfo = commentsListState.layoutInfo
139-
val lastVisibleIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: -1
140-
val totalItemsCount = layoutInfo.totalItemsCount
141-
lastVisibleIndex to totalItemsCount
96+
BackHandler() {
97+
viewModel.backToCommentList()
14298
}
143-
.distinctUntilChanged()
144-
.collect { (lastVisibleIndex, totalItemsCount) ->
145-
val isAtBottom = totalItemsCount > 0 && lastVisibleIndex == totalItemsCount - 1
146-
val hasMoreComments = commentsState.comments.nextPageUrl != null
147-
if (isAtBottom && hasMoreComments && !commentsState.common.isLoading) {
148-
serviceId?.let {
149-
viewModel.loadMoreComments(it)
150-
}
151-
}
152-
}
15399
}
154-
155-
LazyColumn(
156-
modifier = modifier.fillMaxSize(),
157-
state = commentsListState
158-
) {
159-
// Common header
160-
videoDetailCommonHeader(
161-
streamInfo = streamInfo,
162-
navController = navController,
163-
onPlayAudioClick = onPlayAudioClick,
164-
onAddToPlaylistClick = onAddToPlaylistClick
165-
)
166-
167-
// Comments content (inline from CommentList)
168-
commentListContent(
100+
else -> {
101+
CommentList(
169102
comments = commentsState.comments.itemList,
170103
isLoading = commentsState.common.isLoading,
171104
hasMoreComments = commentsState.comments.nextPageUrl != null,
@@ -183,8 +116,8 @@ fun CommentSection(
183116
}
184117
}
185118
},
186-
showStickyHeader = false,
187119
onBackClick = { },
120+
listState = commentsListState,
188121
navController = navController,
189122
onTimestampClick = onTimestampClick
190123
)

0 commit comments

Comments
 (0)