Skip to content

Commit cd28087

Browse files
committed
Replace auto-scroll with manual scroll-to-top FAB in live chat
1 parent 3cb3889 commit cd28087

1 file changed

Lines changed: 84 additions & 30 deletions

File tree

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

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,36 @@
11
package org.schabi.newpipe.ui.components.video.comment
22

3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.fadeIn
5+
import androidx.compose.animation.fadeOut
36
import androidx.compose.foundation.layout.Box
47
import androidx.compose.foundation.layout.fillMaxSize
58
import androidx.compose.foundation.layout.fillMaxWidth
69
import androidx.compose.foundation.layout.heightIn
710
import androidx.compose.foundation.layout.padding
811
import androidx.compose.foundation.lazy.LazyColumn
912
import androidx.compose.foundation.lazy.rememberLazyListState
13+
import androidx.compose.material.icons.Icons
14+
import androidx.compose.material.icons.filled.KeyboardArrowUp
15+
import androidx.compose.material3.BadgedBox
16+
import androidx.compose.material3.FloatingActionButton
17+
import androidx.compose.material3.Icon
1018
import androidx.compose.material3.MaterialTheme
1119
import androidx.compose.material3.Text
1220
import androidx.compose.runtime.Composable
13-
import androidx.compose.runtime.LaunchedEffect
21+
import androidx.compose.runtime.derivedStateOf
1422
import androidx.compose.runtime.getValue
23+
import androidx.compose.runtime.mutableStateOf
24+
import androidx.compose.runtime.remember
25+
import androidx.compose.runtime.rememberCoroutineScope
1526
import androidx.compose.ui.Alignment
1627
import androidx.compose.ui.Modifier
1728
import androidx.compose.ui.input.nestedscroll.nestedScroll
1829
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
1930
import androidx.compose.ui.unit.dp
2031
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2132
import androidx.lifecycle.viewmodel.compose.viewModel
33+
import kotlinx.coroutines.launch
2234
import org.schabi.newpipe.ui.components.common.LazyColumnThemedScrollbar
2335
import org.schabi.newpipe.ui.components.common.LoadingIndicator
2436
import org.schabi.newpipe.viewmodels.LiveChatViewModel
@@ -28,42 +40,84 @@ fun LiveChatSection(liveChatViewModel: LiveChatViewModel = viewModel()) {
2840
val liveChatItems by liveChatViewModel.liveChatItems.collectAsStateWithLifecycle()
2941
val state = rememberLazyListState()
3042
val nestedScrollInterop = rememberNestedScrollInteropConnection()
43+
val coroutineScope = rememberCoroutineScope()
3144

32-
LaunchedEffect(liveChatItems.size) {
33-
if (liveChatItems.isNotEmpty()) {
34-
state.scrollToItem(0)
35-
}
45+
// Track whether user is at the top of the list
46+
val isAtTop by remember { derivedStateOf { state.firstVisibleItemIndex == 0 } }
47+
48+
// Track how many messages were seen while at the top
49+
val lastSeenCount = remember { mutableStateOf(0) }
50+
if (isAtTop && liveChatItems.isNotEmpty()) {
51+
lastSeenCount.value = liveChatItems.size
3652
}
3753

38-
LazyColumnThemedScrollbar(state = state) {
39-
LazyColumn(
40-
modifier = Modifier
41-
.fillMaxSize()
42-
.nestedScroll(nestedScrollInterop),
43-
state = state
44-
) {
45-
item {
46-
Text(
47-
modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 4.dp),
48-
text = "Live Chat",
49-
style = MaterialTheme.typography.titleMedium
50-
)
51-
}
54+
val unreadCount = liveChatItems.size - lastSeenCount.value
5255

53-
if (liveChatItems.isEmpty()) {
56+
Box(modifier = Modifier.fillMaxSize()) {
57+
LazyColumnThemedScrollbar(state = state) {
58+
LazyColumn(
59+
modifier = Modifier
60+
.fillMaxSize()
61+
.nestedScroll(nestedScrollInterop),
62+
state = state
63+
) {
5464
item {
55-
Box(
56-
modifier = Modifier
57-
.fillMaxWidth()
58-
.heightIn(min = 128.dp),
59-
contentAlignment = Alignment.Center
60-
) {
61-
LoadingIndicator()
65+
Text(
66+
modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 4.dp),
67+
text = "Live Chat",
68+
style = MaterialTheme.typography.titleMedium
69+
)
70+
}
71+
72+
if (liveChatItems.isEmpty()) {
73+
item {
74+
Box(
75+
modifier = Modifier
76+
.fillMaxWidth()
77+
.heightIn(min = 128.dp),
78+
contentAlignment = Alignment.Center
79+
) {
80+
LoadingIndicator()
81+
}
82+
}
83+
} else {
84+
items(liveChatItems.size, key = { liveChatItems[it].commentId }) {
85+
Comment(comment = liveChatItems[it]) {}
6286
}
6387
}
64-
} else {
65-
items(liveChatItems.size, key = { liveChatItems[it].commentId }) {
66-
Comment(comment = liveChatItems[it]) {}
88+
}
89+
}
90+
91+
// Floating button to jump to newest messages
92+
AnimatedVisibility(
93+
visible = !isAtTop && unreadCount > 0,
94+
modifier = Modifier
95+
.align(Alignment.BottomEnd)
96+
.padding(16.dp),
97+
enter = fadeIn(),
98+
exit = fadeOut()
99+
) {
100+
FloatingActionButton(
101+
onClick = {
102+
coroutineScope.launch {
103+
state.scrollToItem(0)
104+
}
105+
}
106+
) {
107+
BadgedBox(
108+
badge = {
109+
Text(
110+
text = unreadCount.toString(),
111+
modifier = Modifier.padding(4.dp),
112+
style = MaterialTheme.typography.labelSmall,
113+
color = MaterialTheme.colorScheme.onPrimary
114+
)
115+
}
116+
) {
117+
Icon(
118+
imageVector = Icons.Default.KeyboardArrowUp,
119+
contentDescription = "Scroll to new messages"
120+
)
67121
}
68122
}
69123
}

0 commit comments

Comments
 (0)