android, desktop: add floating date separator to chatview (#4951)

* android, desktop: add floating date separator to chatview

* closer near bottom

* uncessary code

* same pill bg as other btns

* space

* varname

* safe get for lastVisibleItem

* move floating date outside of floating buttons

* fast cleanup on chat change

* reduced recomposes

* change delay position

* base near bottom offset on viewport size

* refactor

* Revert "change delay position"

This reverts commit 27b19580ed.

* simplified

* exact match on header position

* reduce recomposes

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
This commit is contained in:
Diogo
2024-09-30 15:45:32 +01:00
committed by GitHub
parent d9ad755474
commit 533d0e40ac
@@ -12,6 +12,7 @@ import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.*
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.*
import androidx.compose.ui.layout.layoutId
@@ -1250,6 +1251,12 @@ fun BoxWithConstraintsScope.ChatItemsList(
}
}
FloatingButtons(chatModel.chatItems, unreadCount, remoteHostId, chatInfo, searchValue, markRead, setFloatingButton, listState)
FloatingDate(
Modifier.padding(top = 10.dp).align(Alignment.TopCenter),
listState,
)
LaunchedEffect(Unit) {
snapshotFlow { listState.isScrollInProgress }
.collect {
@@ -1476,6 +1483,108 @@ private fun TopEndFloatingButton(
}
}
@Composable
private fun FloatingDate(
modifier: Modifier,
listState: LazyListState,
) {
var nearBottomIndex by remember { mutableStateOf(-1) }
var isNearBottom by remember { mutableStateOf(true) }
val lastVisibleItemDate = remember {
derivedStateOf {
if (listState.layoutInfo.visibleItemsInfo.lastIndex >= 0 && listState.firstVisibleItemIndex >= 0) {
val lastVisibleChatItemIndex = chatModel.chatItems.value.lastIndex - listState.firstVisibleItemIndex - listState.layoutInfo.visibleItemsInfo.lastIndex
val item = chatModel.chatItems.value.getOrNull(lastVisibleChatItemIndex)
val timeZone = TimeZone.currentSystemDefault()
item?.meta?.itemTs?.toLocalDateTime(timeZone)?.date?.atStartOfDayIn(timeZone)
} else {
null
}
}
}
val showDate = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
launch {
snapshotFlow { chatModel.chatId.value }
.distinctUntilChanged()
.collect {
showDate.value = false
isNearBottom = true
nearBottomIndex = -1
}
}
}
LaunchedEffect(Unit) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo }
.collect { visibleItemsInfo ->
if (visibleItemsInfo.find { it.index == 0 } != null) {
var elapsedOffset = 0
for (it in visibleItemsInfo) {
if (elapsedOffset >= listState.layoutInfo.viewportSize.height / 2.5) {
nearBottomIndex = it.index
break;
}
elapsedOffset += it.size
}
}
isNearBottom = if (nearBottomIndex == -1) true else (visibleItemsInfo.firstOrNull()?.index ?: 0) <= nearBottomIndex
}
}
fun setDateVisibility(isVisible: Boolean) {
if (isVisible) {
val now = Clock.System.now()
val date = lastVisibleItemDate.value
if (!isNearBottom && !showDate.value && date != null && getTimestampDateText(date) != getTimestampDateText(now)) {
showDate.value = true
}
} else if (showDate.value) {
showDate.value = false
}
}
LaunchedEffect(Unit) {
var hideDateWhenNotScrolling: Job = Job()
snapshotFlow { listState.firstVisibleItemScrollOffset }
.collect {
setDateVisibility(true)
hideDateWhenNotScrolling.cancel()
hideDateWhenNotScrolling = launch {
delay(1000)
setDateVisibility(false)
}
}
}
AnimatedVisibility(
modifier = modifier,
visible = showDate.value,
enter = fadeIn(tween(durationMillis = 350)),
exit = fadeOut(tween(durationMillis = 350))
) {
val date = lastVisibleItemDate.value
Column {
Text(
text = if (date != null) getTimestampDateText(date) else "",
Modifier
.background(
color = MaterialTheme.colors.secondaryVariant,
RoundedCornerShape(25.dp)
)
.padding(vertical = 4.dp, horizontal = 8.dp)
.clip(RoundedCornerShape(25.dp)),
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center,
color = MaterialTheme.colors.secondary
)
}
}
}
@Composable
private fun DownloadFilesButton(
forwardConfirmation: ForwardConfirmation.FilesNotAccepted,
@@ -1539,7 +1648,7 @@ private fun ButtonRow(horizontalArrangement: Arrangement.Horizontal, content: @C
private fun DateSeparator(date: Instant) {
Text(
text = getTimestampDateText(date),
Modifier.padding(DEFAULT_PADDING).fillMaxWidth(),
Modifier.padding(vertical = DEFAULT_PADDING_HALF + 4.dp, horizontal = DEFAULT_PADDING_HALF).fillMaxWidth(),
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center,