Merge branch 'master' into mentions

This commit is contained in:
Evgeny Poberezkin 2025-01-30 10:07:00 +00:00
commit 4ed67f094f
No known key found for this signature in database
GPG key ID: 494BDDD9A28B577D
5 changed files with 104 additions and 109 deletions

View file

@ -339,7 +339,7 @@ fun AndroidScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
.graphicsLayer { translationX = maxWidth.toPx() - minOf(offset.value.dp, maxWidth).toPx() }
) Box2@{
currentChatId.value?.let {
ChatView(currentChatId, reportsView = false, onComposed = onComposed)
ChatView(currentChatId, contentTag = null, onComposed = onComposed)
}
}
}
@ -393,7 +393,7 @@ fun CenterPartOfScreen() {
ModalManager.center.showInView()
}
}
else -> ChatView(currentChatId, reportsView = false) {}
else -> ChatView(currentChatId, contentTag = null) {}
}
}

View file

@ -59,7 +59,7 @@ data class ItemSeparation(val timestamp: Boolean, val largeGap: Boolean, val dat
// to chat list smooth. Otherwise, chat view will become blank right before the transition starts
fun ChatView(
staleChatId: State<String?>,
reportsView: Boolean,
contentTag: MsgContentTag?,
scrollToItemId: MutableState<Long?> = remember { mutableStateOf(null) },
onComposed: suspend (chatId: String) -> Unit
) {
@ -67,7 +67,6 @@ fun ChatView(
// They have their own iterator inside for a reason to prevent crash "Reading a state that was created after the snapshot..."
val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } }
val activeChatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatInfo } }
val activeChatStats = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats } }
val user = chatModel.currentUser.value
val chatInfo = activeChatInfo.value
if (chatInfo == null || user == null) {
@ -76,11 +75,6 @@ fun ChatView(
ModalManager.end.closeModals()
}
} else {
val groupReports = remember { derivedStateOf {
val reportsCount = if (activeChatInfo.value is ChatInfo.Group) activeChatStats.value?.reportsCount ?: 0 else 0
GroupReports(reportsCount, reportsView) }
}
val reversedChatItems = remember { derivedStateOf { chatModel.chatItemsForContent(groupReports.value.contentTag).value.asReversed() } }
val searchText = rememberSaveable { mutableStateOf("") }
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
val composeState = rememberSaveable(saver = ComposeState.saver()) {
@ -106,7 +100,7 @@ fun ChatView(
.distinctUntilChanged()
.filterNotNull()
.collect { chatId ->
if (!groupReports.value.reportsView) {
if (contentTag == null) {
markUnreadChatAsRead(chatId)
}
showSearch.value = false
@ -121,13 +115,13 @@ fun ChatView(
// Having activeChat reloaded on every change in it is inefficient (UI lags)
val unreadCount = remember {
derivedStateOf {
chatModel.chatsForContent(if (reportsView) MsgContentTag.Report else null).value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats?.unreadCount ?: 0
chatModel.chatsForContent(contentTag).value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats?.unreadCount ?: 0
}
}
val clipboard = LocalClipboardManager.current
CompositionLocalProvider(
LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false),
LocalContentTag provides groupReports.value.contentTag
LocalContentTag provides contentTag
) {
when (chatInfo) {
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
@ -141,14 +135,13 @@ fun ChatView(
val c = chatModel.getChat(chatInfo.id) ?: return@onSearchValueChanged
if (chatModel.chatId.value != chatInfo.id) return@onSearchValueChanged
withBGApi {
apiFindMessages(c, value, groupReports.value.toContentTag())
apiFindMessages(c, value, contentTag)
searchText.value = value
}
}
ChatLayout(
remoteHostId = remoteHostId,
chatInfo = activeChatInfo,
reversedChatItems = reversedChatItems,
unreadCount,
composeState,
composeView = {
@ -177,7 +170,7 @@ fun ChatView(
}
} else {
SelectedItemsBottomToolbar(
reversedChatItems = reversedChatItems,
contentTag = contentTag,
selectedChatItems = selectedChatItems,
chatInfo = chatInfo,
deleteItems = { canDeleteForAll ->
@ -238,7 +231,6 @@ fun ChatView(
)
}
},
groupReports,
scrollToItemId,
attachmentOption,
attachmentBottomSheetState,
@ -333,7 +325,7 @@ fun ChatView(
setGroupMembers(chatRh, groupInfo, chatModel)
if (!isActive) return@launch
if (!groupReports.value.reportsView) {
if (contentTag == null) {
ModalManager.end.closeModals()
}
ModalManager.end.showModalCloseable(true) { close ->
@ -347,12 +339,12 @@ fun ChatView(
val c = chatModel.getChat(chatId)
if (chatModel.chatId.value != chatId) return@ChatLayout
if (c != null) {
apiLoadMessages(c.remoteHostId, c.chatInfo.chatType, c.chatInfo.apiId, groupReports.value.toContentTag(), pagination, searchText.value, visibleItemIndexes)
apiLoadMessages(c.remoteHostId, c.chatInfo.chatType, c.chatInfo.apiId, contentTag, pagination, searchText.value, visibleItemIndexes)
}
},
deleteMessage = { itemId, mode ->
withBGApi {
val toDeleteItem = reversedChatItems.value.lastOrNull { it.id == itemId }
val toDeleteItem = reversedChatItemsStatic(contentTag).lastOrNull { it.id == itemId }
val toModerate = toDeleteItem?.memberToModerate(chatInfo)
val groupInfo = toModerate?.first
val groupMember = toModerate?.second
@ -660,11 +652,9 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType)
fun ChatLayout(
remoteHostId: State<Long?>,
chatInfo: State<ChatInfo?>,
reversedChatItems: State<List<ChatItem>>,
unreadCount: State<Int>,
composeState: MutableState<ComposeState>,
composeView: (@Composable () -> Unit),
groupReports: State<GroupReports>,
scrollToItemId: MutableState<Long?>,
attachmentOption: MutableState<AttachmentOption?>,
attachmentBottomSheetState: ModalBottomSheetState,
@ -735,7 +725,8 @@ fun ChatLayout(
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
val composeViewHeight = remember { mutableStateOf(0.dp) }
Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, !groupReports.value.reportsView)) {
val contentTag = LocalContentTag.current
Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, contentTag == null)) {
val remoteHostId = remember { remoteHostId }.value
val chatInfo = remember { chatInfo }.value
val oneHandUI = remember { appPrefs.oneHandUI.state }
@ -748,8 +739,8 @@ fun ChatLayout(
override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float = 0f
}) {
ChatItemsList(
remoteHostId, chatInfo, reversedChatItems, unreadCount, composeState, composeViewHeight, searchValue,
useLinkPreviews, linkMode, groupReports, scrollToItemId, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages,
remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue,
useLinkPreviews, linkMode, scrollToItemId, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages,
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
setReaction, showItemDetails, markItemsRead, markChatRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
@ -757,7 +748,7 @@ fun ChatLayout(
}
}
}
if (groupReports.value.reportsView) {
if (contentTag == MsgContentTag.Report) {
Column(
Modifier
.layoutId(CHAT_COMPOSE_LAYOUT_ID)
@ -768,7 +759,7 @@ fun ChatLayout(
AnimatedVisibility(selectedChatItems.value != null) {
if (chatInfo != null) {
SelectedItemsBottomToolbar(
reversedChatItems = reversedChatItems,
contentTag = contentTag,
selectedChatItems = selectedChatItems,
chatInfo = chatInfo,
deleteItems = { _ ->
@ -805,23 +796,24 @@ fun ChatLayout(
}
}
}
val reportsCount = reportsCount(chatInfo?.id)
if (oneHandUI.value && chatBottomBar.value) {
if (groupReports.value.showBar) {
ReportedCountToolbar(groupReports, withStatusBar = true, showGroupReports)
if (contentTag == null && reportsCount > 0) {
ReportedCountToolbar(reportsCount, withStatusBar = true, showGroupReports)
} else {
StatusBarBackground()
}
} else {
NavigationBarBackground(true, oneHandUI.value, noAlpha = true)
}
if (groupReports.value.reportsView) {
if (contentTag == MsgContentTag.Report) {
if (oneHandUI.value) {
StatusBarBackground()
}
Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
Box {
if (selectedChatItems.value == null) {
GroupReportsAppBar(groupReports, { ModalManager.end.closeModal() }, onSearchValueChanged)
GroupReportsAppBar(contentTag, { ModalManager.end.closeModal() }, onSearchValueChanged)
} else {
SelectedItemsTopToolbar(selectedChatItems, !oneHandUI.value)
}
@ -832,14 +824,14 @@ fun ChatLayout(
Box {
if (selectedChatItems.value == null) {
if (chatInfo != null) {
ChatInfoToolbar(chatInfo, groupReports, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
ChatInfoToolbar(chatInfo, contentTag, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
}
} else {
SelectedItemsTopToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value)
}
}
if (groupReports.value.showBar && (!oneHandUI.value || !chatBottomBar.value)) {
ReportedCountToolbar(groupReports, withStatusBar = false, showGroupReports)
if (contentTag == null && reportsCount > 0 && (!oneHandUI.value || !chatBottomBar.value)) {
ReportedCountToolbar(reportsCount, withStatusBar = false, showGroupReports)
}
}
}
@ -851,7 +843,7 @@ fun ChatLayout(
@Composable
fun BoxScope.ChatInfoToolbar(
chatInfo: ChatInfo,
groupReports: State<GroupReports>,
contentTag: MsgContentTag?,
back: () -> Unit,
info: () -> Unit,
startCall: (CallMediaType) -> Unit,
@ -873,7 +865,7 @@ fun BoxScope.ChatInfoToolbar(
showSearch.value = false
}
}
if (appPlatform.isAndroid && !groupReports.value.reportsView) {
if (appPlatform.isAndroid && contentTag == null) {
BackHandler(onBack = onBackClicked)
}
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
@ -1069,7 +1061,7 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
@Composable
private fun ReportedCountToolbar(
groupReports: State<GroupReports>,
reportsCount: Int,
withStatusBar: Boolean,
showGroupReports: () -> Unit
) {
@ -1087,12 +1079,11 @@ private fun ReportedCountToolbar(
) {
Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.error)
Spacer(Modifier.width(4.dp))
val reports = groupReports.value.reportsCount
Text(
if (reports == 1) {
if (reportsCount == 1) {
stringResource(MR.strings.group_reports_active_one)
} else {
stringResource(MR.strings.group_reports_active).format(reports)
stringResource(MR.strings.group_reports_active).format(reportsCount)
},
style = MaterialTheme.typography.button
)
@ -1106,21 +1097,19 @@ private fun ContactVerifiedShield() {
Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(18.dp * fontSizeSqrtMultiplier).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary)
}
/** Saves current scroll position when [GroupReports] are open and user opens [ChatItemInfoView], for example, and goes back */
/** Saves current scroll position when group reports are open and user opens [ChatItemInfoView], for example, and goes back */
private var reportsListState: LazyListState? = null
@Composable
fun BoxScope.ChatItemsList(
remoteHostId: Long?,
chatInfo: ChatInfo,
reversedChatItems: State<List<ChatItem>>,
unreadCount: State<Int>,
composeState: MutableState<ComposeState>,
composeViewHeight: State<Dp>,
searchValue: State<String>,
useLinkPreviews: Boolean,
linkMode: SimplexLinkMode,
groupReports: State<GroupReports>,
scrollToItemId: MutableState<Long?>,
selectedChatItems: MutableState<Set<Long>?>,
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
@ -1151,8 +1140,17 @@ fun BoxScope.ChatItemsList(
) {
val searchValueIsEmpty = remember { derivedStateOf { searchValue.value.isEmpty() } }
val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf<Long>()) }
val mergedItems = remember { derivedStateOf { MergedItems.create(reversedChatItems.value, unreadCount, revealedItems.value, chatModel.chatStateForContent(groupReports.value.contentTag)) } }
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar).roundToPx() })
val contentTag = LocalContentTag.current
// not using reversedChatItems inside to prevent possible derivedState bug in Compose when one derived state access can cause crash asking another derived state
val mergedItems = remember {
derivedStateOf {
MergedItems.create(chatModel.chatItemsForContent(contentTag).value.asReversed(), unreadCount, revealedItems.value, chatModel.chatStateForContent(contentTag))
}
}
val reversedChatItems = remember { derivedStateOf { chatModel.chatItemsForContent(contentTag).value.asReversed() } }
val reportsCount = reportsCount(chatInfo.id)
val topPaddingToContent = topPaddingToContent(chatView = contentTag == null, contentTag == null && reportsCount > 0)
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() })
/** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of
* [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears
* */
@ -1171,7 +1169,7 @@ fun BoxScope.ChatItemsList(
LazyListState(index + 1, -maxHeightForList.value)
}
})
SaveReportsStateOnDispose(groupReports, listState)
SaveReportsStateOnDispose(listState)
val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } }
val loadingMoreItems = remember { mutableStateOf(false) }
val animatedScrollingInProgress = remember { mutableStateOf(false) }
@ -1181,7 +1179,7 @@ fun BoxScope.ChatItemsList(
ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect)
}
if (!loadingMoreItems.value) {
PreloadItems(chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), reversedChatItems, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination ->
PreloadItems(chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), contentTag, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination ->
if (loadingMoreItems.value) return@PreloadItems false
try {
loadingMoreItems.value = true
@ -1201,11 +1199,11 @@ fun BoxScope.ChatItemsList(
val scope = rememberCoroutineScope()
val scrollToItem: (Long) -> Unit = remember {
// In group reports just set the itemId to scroll to so the main ChatView will handle scrolling
if (groupReports.value.reportsView) return@remember { scrollToItemId.value = it }
if (contentTag == MsgContentTag.Report) return@remember { scrollToItemId.value = it }
scrollToItem(searchValue, loadingMoreItems, animatedScrollingInProgress, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages)
}
val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem, groupReports.value.contentTag) }
if (!groupReports.value.reportsView) {
val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem, contentTag) }
if (contentTag == null) {
LaunchedEffect(Unit) { snapshotFlow { scrollToItemId.value }.filterNotNull().collect {
if (appPlatform.isAndroid) {
ModalManager.end.closeModals()
@ -1214,18 +1212,18 @@ fun BoxScope.ChatItemsList(
scrollToItemId.value = null }
}
}
LoadLastItems(loadingMoreItems, remoteHostId, chatInfo, groupReports)
LoadLastItems(loadingMoreItems, remoteHostId, chatInfo)
SmallScrollOnNewMessage(listState, reversedChatItems)
val finishedInitialComposition = remember { mutableStateOf(false) }
NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed)
DisposableEffectOnGone(
always = {
chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(groupReports.value.contentTag)), groupReports.value.contentTag)
chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(contentTag)), contentTag)
},
whenGone = {
VideoPlayerHolder.releaseAll()
chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(groupReports.value.contentTag)), groupReports.value.contentTag)
chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(contentTag)), contentTag)
}
)
@ -1473,12 +1471,12 @@ fun BoxScope.ChatItemsList(
Modifier.align(Alignment.BottomCenter),
state = listState.value,
contentPadding = PaddingValues(
top = topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar),
top = topPaddingToContent,
bottom = composeViewHeight.value
),
reverseLayout = true,
additionalBarOffset = composeViewHeight,
additionalTopBar = remember { derivedStateOf { groupReports.value.showBar } },
additionalTopBar = rememberUpdatedState(contentTag == null && reportsCount > 0),
chatBottomBar = remember { appPrefs.chatBottomBar.state }
) {
val mergedItemsValue = mergedItems.value
@ -1522,8 +1520,8 @@ fun BoxScope.ChatItemsList(
}
}
}
FloatingButtons(loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, groupReports, markChatRead, listState)
FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar)).align(Alignment.TopCenter), mergedItems, listState, groupReports)
FloatingButtons(topPaddingToContent, topPaddingToContentPx, loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState)
FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState)
LaunchedEffect(Unit) {
snapshotFlow { listState.value.isScrollInProgress }
@ -1543,14 +1541,15 @@ fun BoxScope.ChatItemsList(
}
@Composable
private fun LoadLastItems(loadingMoreItems: MutableState<Boolean>, remoteHostId: Long?, chatInfo: ChatInfo, groupReports: State<GroupReports>) {
private fun LoadLastItems(loadingMoreItems: MutableState<Boolean>, remoteHostId: Long?, chatInfo: ChatInfo) {
val contentTag = LocalContentTag.current
LaunchedEffect(remoteHostId, chatInfo.id) {
try {
loadingMoreItems.value = true
if (chatModel.chatStateForContent(groupReports.value.contentTag).totalAfter.value <= 0) return@LaunchedEffect
if (chatModel.chatStateForContent(contentTag).totalAfter.value <= 0) return@LaunchedEffect
delay(500)
withContext(Dispatchers.Default) {
apiLoadMessages(remoteHostId, chatInfo.chatType, chatInfo.apiId, groupReports.value.toContentTag(), ChatPagination.Last(ChatPagination.INITIAL_COUNT))
apiLoadMessages(remoteHostId, chatInfo.chatType, chatInfo.apiId, contentTag, ChatPagination.Last(ChatPagination.INITIAL_COUNT))
}
} finally {
loadingMoreItems.value = false
@ -1616,6 +1615,8 @@ private fun NotifyChatListOnFinishingComposition(
@Composable
fun BoxScope.FloatingButtons(
topPaddingToContent: Dp,
topPaddingToContentPx: State<Int>,
loadingMoreItems: MutableState<Boolean>,
animatedScrollingInProgress: MutableState<Boolean>,
mergedItems: State<MergedItems>,
@ -1623,12 +1624,10 @@ fun BoxScope.FloatingButtons(
maxHeight: State<Int>,
composeViewHeight: State<Dp>,
searchValue: State<String>,
groupReports: State<GroupReports>,
markChatRead: () -> Unit,
listState: State<LazyListState>
) {
val scope = rememberCoroutineScope()
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar).roundToPx() })
val bottomUnreadCount = remember {
derivedStateOf {
if (unreadCount.value == 0) return@derivedStateOf 0
@ -1674,7 +1673,7 @@ fun BoxScope.FloatingButtons(
val showDropDown = remember { mutableStateOf(false) }
TopEndFloatingButton(
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar)).align(Alignment.TopEnd),
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent).align(Alignment.TopEnd),
topUnreadCount,
animatedScrollingInProgress,
onClick = {
@ -1696,7 +1695,7 @@ fun BoxScope.FloatingButtons(
DefaultDropdownMenu(
showDropDown,
modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) } },
offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar))
offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent)
) {
ItemAction(
generalGetString(MR.strings.mark_read),
@ -1713,7 +1712,7 @@ fun BoxScope.FloatingButtons(
fun PreloadItems(
chatId: String,
ignoreLoadingRequests: MutableSet<Long>,
reversedChatItems: State<List<ChatItem>>,
contentTag: MsgContentTag?,
mergedItems: State<MergedItems>,
listState: State<LazyListState>,
remaining: Int,
@ -1724,8 +1723,8 @@ fun PreloadItems(
val chatId = rememberUpdatedState(chatId)
val loadItems = rememberUpdatedState(loadItems)
val ignoreLoadingRequests = rememberUpdatedState(ignoreLoadingRequests)
PreloadItemsBefore(allowLoad, chatId, ignoreLoadingRequests, reversedChatItems, mergedItems, listState, remaining, loadItems)
PreloadItemsAfter(allowLoad, chatId, reversedChatItems, mergedItems, listState, remaining, loadItems)
PreloadItemsBefore(allowLoad, chatId, ignoreLoadingRequests, contentTag, mergedItems, listState, remaining, loadItems)
PreloadItemsAfter(allowLoad, chatId, contentTag, mergedItems, listState, remaining, loadItems)
}
@Composable
@ -1733,7 +1732,7 @@ private fun PreloadItemsBefore(
allowLoad: State<Boolean>,
chatId: State<String>,
ignoreLoadingRequests: State<MutableSet<Long>>,
reversedChatItems: State<List<ChatItem>>,
contentTag: MsgContentTag?,
mergedItems: State<MergedItems>,
listState: State<LazyListState>,
remaining: Int,
@ -1746,12 +1745,12 @@ private fun PreloadItemsBefore(
val splits = mergedItems.value.splits
val lastVisibleIndex = (listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0)
var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits)
val items = reversedChatItems.value
val items = reversedChatItemsStatic(contentTag)
if (splits.isEmpty() && items.isNotEmpty() && lastVisibleIndex > mergedItems.value.items.size - remaining) {
lastIndexToLoadFrom = 0
lastIndexToLoadFrom = items.lastIndex
}
if (allowLoad.value && lastIndexToLoadFrom != null) {
items.getOrNull(items.lastIndex - lastIndexToLoadFrom)?.id
items.getOrNull(lastIndexToLoadFrom)?.id
} else {
null
}
@ -1760,10 +1759,12 @@ private fun PreloadItemsBefore(
.filter { !ignoreLoadingRequests.value.contains(it) }
.collect { loadFromItemId ->
withBGApi {
val sizeWas = reversedChatItems.value.size
val oldestItemIdWas = reversedChatItems.value.lastOrNull()?.id
val items = reversedChatItemsStatic(contentTag)
val sizeWas = items.size
val oldestItemIdWas = items.lastOrNull()?.id
val triedToLoad = loadItems.value(chatId.value, ChatPagination.Before(loadFromItemId, ChatPagination.PRELOAD_COUNT))
if (triedToLoad && sizeWas == reversedChatItems.value.size && oldestItemIdWas == reversedChatItems.value.lastOrNull()?.id) {
val itemsUpdated = reversedChatItemsStatic(contentTag)
if (triedToLoad && sizeWas == itemsUpdated.size && oldestItemIdWas == itemsUpdated.lastOrNull()?.id) {
ignoreLoadingRequests.value.add(loadFromItemId)
}
}
@ -1775,7 +1776,7 @@ private fun PreloadItemsBefore(
private fun PreloadItemsAfter(
allowLoad: MutableState<Boolean>,
chatId: State<String>,
reversedChatItems: State<List<ChatItem>>,
contentTag: MsgContentTag?,
mergedItems: State<MergedItems>,
listState: State<LazyListState>,
remaining: Int,
@ -1796,7 +1797,7 @@ private fun PreloadItemsAfter(
snapshotFlow { listState.value.firstVisibleItemIndex }
.distinctUntilChanged()
.map { firstVisibleIndex ->
val items = reversedChatItems.value
val items = reversedChatItemsStatic(contentTag)
val splits = mergedItems.value.splits
val split = splits.lastOrNull { it.indexRangeInParentItems.contains(firstVisibleIndex) }
// we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom)
@ -1864,15 +1865,14 @@ fun topPaddingToContent(chatView: Boolean, additionalTopBar: Boolean = false): D
@Composable
private fun FloatingDate(
modifier: Modifier,
topPaddingToContentPx: State<Int>,
mergedItems: State<MergedItems>,
listState: State<LazyListState>,
groupReports: State<GroupReports>
) {
val isNearBottom = remember(chatModel.chatId) { mutableStateOf(listState.value.firstVisibleItemIndex == 0) }
val nearBottomIndex = remember(chatModel.chatId) { mutableStateOf(if (isNearBottom.value) -1 else 0) }
val showDate = remember(chatModel.chatId) { mutableStateOf(false) }
val density = LocalDensity.current.density
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar).roundToPx() })
val fontSizeSqrtMultiplier = fontSizeSqrtMultiplier
val lastVisibleItemDate = remember {
derivedStateOf {
@ -1957,10 +1957,11 @@ private fun FloatingDate(
}
@Composable
private fun SaveReportsStateOnDispose(groupReports: State<GroupReports>, listState: State<LazyListState>) {
private fun SaveReportsStateOnDispose(listState: State<LazyListState>) {
val contentTag = LocalContentTag.current
DisposableEffect(Unit) {
onDispose {
reportsListState = if (groupReports.value.reportsView && ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) listState.value else null
reportsListState = if (contentTag == MsgContentTag.Report && ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) listState.value else null
}
}
}
@ -2062,6 +2063,18 @@ private fun MarkItemsReadAfterDelay(
}
}
@Composable
fun reportsCount(staleChatId: String?): Int {
return if (staleChatId?.startsWith("#") != true) {
0
} else {
remember(staleChatId) { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId }?.chatStats } }.value?.reportsCount ?: 0
}
}
private fun reversedChatItemsStatic(contentTag: MsgContentTag?): List<ChatItem> =
chatModel.chatItemsForContent(contentTag).value.asReversed()
private fun oldestPartiallyVisibleListItemInListStateOrNull(topPaddingToContentPx: State<Int>, mergedItems: State<MergedItems>, listState: State<LazyListState>): ListItem? {
val lastFullyVisibleOffset = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value
return mergedItems.value.items.getOrNull((listState.value.layoutInfo.visibleItemsInfo.lastOrNull { item ->
@ -2665,11 +2678,9 @@ fun PreviewChatLayout() {
ChatLayout(
remoteHostId = remember { mutableStateOf(null) },
chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) },
reversedChatItems = remember { mutableStateOf(emptyList()) },
unreadCount = unreadCount,
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
composeView = {},
groupReports = remember { mutableStateOf(GroupReports(0, false)) },
scrollToItemId = remember { mutableStateOf(null) },
attachmentOption = remember { mutableStateOf<AttachmentOption?>(null) },
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
@ -2742,11 +2753,9 @@ fun PreviewGroupChatLayout() {
ChatLayout(
remoteHostId = remember { mutableStateOf(null) },
chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) },
reversedChatItems = remember { mutableStateOf(emptyList()) },
unreadCount = unreadCount,
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
composeView = {},
groupReports = remember { mutableStateOf(GroupReports(0, false)) },
scrollToItemId = remember { mutableStateOf(null) },
attachmentOption = remember { mutableStateOf<AttachmentOption?>(null) },
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),

View file

@ -48,7 +48,7 @@ fun BoxScope.SelectedItemsTopToolbar(selectedChatItems: MutableState<Set<Long>?>
@Composable
fun SelectedItemsBottomToolbar(
chatInfo: ChatInfo,
reversedChatItems: State<List<ChatItem>>,
contentTag: MsgContentTag?,
selectedChatItems: MutableState<Set<Long>?>,
deleteItems: (Boolean) -> Unit, // Boolean - delete for everyone is possible
moderateItems: () -> Unit,
@ -107,8 +107,9 @@ fun SelectedItemsBottomToolbar(
}
Divider(Modifier.align(Alignment.TopStart))
}
LaunchedEffect(chatInfo, reversedChatItems.value, selectedChatItems.value) {
recheckItems(chatInfo, reversedChatItems.value.asReversed(), selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited)
val chatItems = remember { derivedStateOf { chatModel.chatItemsForContent(contentTag).value } }
LaunchedEffect(chatInfo, chatItems.value, selectedChatItems.value) {
recheckItems(chatInfo, chatItems.value, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited)
}
}

View file

@ -16,28 +16,14 @@ import kotlinx.coroutines.flow.*
val LocalContentTag: ProvidableCompositionLocal<MsgContentTag?> = staticCompositionLocalOf { null }
data class GroupReports(
val reportsCount: Int,
val reportsView: Boolean,
) {
val showBar: Boolean = reportsCount > 0 && !reportsView
fun toContentTag(): MsgContentTag? {
if (!reportsView) return null
return MsgContentTag.Report
}
val contentTag: MsgContentTag? = if (!reportsView) null else MsgContentTag.Report
}
@Composable
private fun GroupReportsView(staleChatId: State<String?>, scrollToItemId: MutableState<Long?>) {
ChatView(staleChatId, reportsView = true, scrollToItemId, onComposed = {})
ChatView(staleChatId, contentTag = MsgContentTag.Report, scrollToItemId, onComposed = {})
}
@Composable
fun GroupReportsAppBar(
groupReports: State<GroupReports>,
contentTag: MsgContentTag?,
close: () -> Unit,
onSearchValueChanged: (String) -> Unit
) {
@ -65,11 +51,11 @@ fun GroupReportsAppBar(
}
}
)
ItemsReload(groupReports)
ItemsReload(contentTag)
}
@Composable
private fun ItemsReload(groupReports: State<GroupReports>) {
private fun ItemsReload(contentTag: MsgContentTag?) {
LaunchedEffect(Unit) {
snapshotFlow { chatModel.chatId.value }
.distinctUntilChanged()
@ -79,7 +65,7 @@ private fun ItemsReload(groupReports: State<GroupReports>) {
.filterNotNull()
.filter { it.chatInfo is ChatInfo.Group }
.collect { chat ->
reloadItems(chat, groupReports)
reloadItems(chat, contentTag)
}
}
}
@ -100,7 +86,6 @@ suspend fun showGroupReportsView(staleChatId: State<String?>, scrollToItemId: Mu
}
}
private suspend fun reloadItems(chat: Chat, groupReports: State<GroupReports>) {
val contentFilter = groupReports.value.toContentTag()
apiLoadMessages(chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId, contentFilter, ChatPagination.Initial(ChatPagination.INITIAL_COUNT))
private suspend fun reloadItems(chat: Chat, contentTag: MsgContentTag?) {
apiLoadMessages(chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId, contentTag, ChatPagination.Initial(ChatPagination.INITIAL_COUNT))
}

View file

@ -95,7 +95,7 @@ private fun MergedMarkedDeletedText(chatItem: ChatItem, chatInfo: ChatInfo, reve
fun markedDeletedText(cItem: ChatItem, chatInfo: ChatInfo): String =
if (cItem.meta.itemDeleted != null && cItem.isReport) {
if (cItem.meta.itemDeleted is CIDeleted.Moderated && cItem.meta.itemDeleted.byGroupMember.groupMemberId != (chatInfo as ChatInfo.Group?)?.groupInfo?.membership?.groupMemberId) {
if (cItem.meta.itemDeleted is CIDeleted.Moderated && cItem.meta.itemDeleted.byGroupMember.groupMemberId != (chatInfo as? ChatInfo.Group)?.groupInfo?.membership?.groupMemberId) {
generalGetString(MR.strings.report_item_archived_by).format(cItem.meta.itemDeleted.byGroupMember.displayName)
} else {
generalGetString(MR.strings.report_item_archived)