11package 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
36import androidx.compose.foundation.layout.Box
47import androidx.compose.foundation.layout.fillMaxSize
58import androidx.compose.foundation.layout.fillMaxWidth
69import androidx.compose.foundation.layout.heightIn
710import androidx.compose.foundation.layout.padding
811import androidx.compose.foundation.lazy.LazyColumn
912import 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
1018import androidx.compose.material3.MaterialTheme
1119import androidx.compose.material3.Text
1220import androidx.compose.runtime.Composable
13- import androidx.compose.runtime.LaunchedEffect
21+ import androidx.compose.runtime.derivedStateOf
1422import androidx.compose.runtime.getValue
23+ import androidx.compose.runtime.mutableStateOf
24+ import androidx.compose.runtime.remember
25+ import androidx.compose.runtime.rememberCoroutineScope
1526import androidx.compose.ui.Alignment
1627import androidx.compose.ui.Modifier
1728import androidx.compose.ui.input.nestedscroll.nestedScroll
1829import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
1930import androidx.compose.ui.unit.dp
2031import androidx.lifecycle.compose.collectAsStateWithLifecycle
2132import androidx.lifecycle.viewmodel.compose.viewModel
33+ import kotlinx.coroutines.launch
2234import org.schabi.newpipe.ui.components.common.LazyColumnThemedScrollbar
2335import org.schabi.newpipe.ui.components.common.LoadingIndicator
2436import 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