mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
Merge branch 'master' into mentions
This commit is contained in:
commit
4ed67f094f
5 changed files with 104 additions and 109 deletions
|
@ -339,7 +339,7 @@ fun AndroidScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
||||||
.graphicsLayer { translationX = maxWidth.toPx() - minOf(offset.value.dp, maxWidth).toPx() }
|
.graphicsLayer { translationX = maxWidth.toPx() - minOf(offset.value.dp, maxWidth).toPx() }
|
||||||
) Box2@{
|
) Box2@{
|
||||||
currentChatId.value?.let {
|
currentChatId.value?.let {
|
||||||
ChatView(currentChatId, reportsView = false, onComposed = onComposed)
|
ChatView(currentChatId, contentTag = null, onComposed = onComposed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -393,7 +393,7 @@ fun CenterPartOfScreen() {
|
||||||
ModalManager.center.showInView()
|
ModalManager.center.showInView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> ChatView(currentChatId, reportsView = false) {}
|
else -> ChatView(currentChatId, contentTag = null) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
// to chat list smooth. Otherwise, chat view will become blank right before the transition starts
|
||||||
fun ChatView(
|
fun ChatView(
|
||||||
staleChatId: State<String?>,
|
staleChatId: State<String?>,
|
||||||
reportsView: Boolean,
|
contentTag: MsgContentTag?,
|
||||||
scrollToItemId: MutableState<Long?> = remember { mutableStateOf(null) },
|
scrollToItemId: MutableState<Long?> = remember { mutableStateOf(null) },
|
||||||
onComposed: suspend (chatId: String) -> Unit
|
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..."
|
// 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 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 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 user = chatModel.currentUser.value
|
||||||
val chatInfo = activeChatInfo.value
|
val chatInfo = activeChatInfo.value
|
||||||
if (chatInfo == null || user == null) {
|
if (chatInfo == null || user == null) {
|
||||||
|
@ -76,11 +75,6 @@ fun ChatView(
|
||||||
ModalManager.end.closeModals()
|
ModalManager.end.closeModals()
|
||||||
}
|
}
|
||||||
} else {
|
} 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 searchText = rememberSaveable { mutableStateOf("") }
|
||||||
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
|
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
|
||||||
val composeState = rememberSaveable(saver = ComposeState.saver()) {
|
val composeState = rememberSaveable(saver = ComposeState.saver()) {
|
||||||
|
@ -106,7 +100,7 @@ fun ChatView(
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.collect { chatId ->
|
.collect { chatId ->
|
||||||
if (!groupReports.value.reportsView) {
|
if (contentTag == null) {
|
||||||
markUnreadChatAsRead(chatId)
|
markUnreadChatAsRead(chatId)
|
||||||
}
|
}
|
||||||
showSearch.value = false
|
showSearch.value = false
|
||||||
|
@ -121,13 +115,13 @@ fun ChatView(
|
||||||
// Having activeChat reloaded on every change in it is inefficient (UI lags)
|
// Having activeChat reloaded on every change in it is inefficient (UI lags)
|
||||||
val unreadCount = remember {
|
val unreadCount = remember {
|
||||||
derivedStateOf {
|
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
|
val clipboard = LocalClipboardManager.current
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false),
|
LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false),
|
||||||
LocalContentTag provides groupReports.value.contentTag
|
LocalContentTag provides contentTag
|
||||||
) {
|
) {
|
||||||
when (chatInfo) {
|
when (chatInfo) {
|
||||||
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
|
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
|
||||||
|
@ -141,14 +135,13 @@ fun ChatView(
|
||||||
val c = chatModel.getChat(chatInfo.id) ?: return@onSearchValueChanged
|
val c = chatModel.getChat(chatInfo.id) ?: return@onSearchValueChanged
|
||||||
if (chatModel.chatId.value != chatInfo.id) return@onSearchValueChanged
|
if (chatModel.chatId.value != chatInfo.id) return@onSearchValueChanged
|
||||||
withBGApi {
|
withBGApi {
|
||||||
apiFindMessages(c, value, groupReports.value.toContentTag())
|
apiFindMessages(c, value, contentTag)
|
||||||
searchText.value = value
|
searchText.value = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ChatLayout(
|
ChatLayout(
|
||||||
remoteHostId = remoteHostId,
|
remoteHostId = remoteHostId,
|
||||||
chatInfo = activeChatInfo,
|
chatInfo = activeChatInfo,
|
||||||
reversedChatItems = reversedChatItems,
|
|
||||||
unreadCount,
|
unreadCount,
|
||||||
composeState,
|
composeState,
|
||||||
composeView = {
|
composeView = {
|
||||||
|
@ -177,7 +170,7 @@ fun ChatView(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SelectedItemsBottomToolbar(
|
SelectedItemsBottomToolbar(
|
||||||
reversedChatItems = reversedChatItems,
|
contentTag = contentTag,
|
||||||
selectedChatItems = selectedChatItems,
|
selectedChatItems = selectedChatItems,
|
||||||
chatInfo = chatInfo,
|
chatInfo = chatInfo,
|
||||||
deleteItems = { canDeleteForAll ->
|
deleteItems = { canDeleteForAll ->
|
||||||
|
@ -238,7 +231,6 @@ fun ChatView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
groupReports,
|
|
||||||
scrollToItemId,
|
scrollToItemId,
|
||||||
attachmentOption,
|
attachmentOption,
|
||||||
attachmentBottomSheetState,
|
attachmentBottomSheetState,
|
||||||
|
@ -333,7 +325,7 @@ fun ChatView(
|
||||||
setGroupMembers(chatRh, groupInfo, chatModel)
|
setGroupMembers(chatRh, groupInfo, chatModel)
|
||||||
if (!isActive) return@launch
|
if (!isActive) return@launch
|
||||||
|
|
||||||
if (!groupReports.value.reportsView) {
|
if (contentTag == null) {
|
||||||
ModalManager.end.closeModals()
|
ModalManager.end.closeModals()
|
||||||
}
|
}
|
||||||
ModalManager.end.showModalCloseable(true) { close ->
|
ModalManager.end.showModalCloseable(true) { close ->
|
||||||
|
@ -347,12 +339,12 @@ fun ChatView(
|
||||||
val c = chatModel.getChat(chatId)
|
val c = chatModel.getChat(chatId)
|
||||||
if (chatModel.chatId.value != chatId) return@ChatLayout
|
if (chatModel.chatId.value != chatId) return@ChatLayout
|
||||||
if (c != null) {
|
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 ->
|
deleteMessage = { itemId, mode ->
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val toDeleteItem = reversedChatItems.value.lastOrNull { it.id == itemId }
|
val toDeleteItem = reversedChatItemsStatic(contentTag).lastOrNull { it.id == itemId }
|
||||||
val toModerate = toDeleteItem?.memberToModerate(chatInfo)
|
val toModerate = toDeleteItem?.memberToModerate(chatInfo)
|
||||||
val groupInfo = toModerate?.first
|
val groupInfo = toModerate?.first
|
||||||
val groupMember = toModerate?.second
|
val groupMember = toModerate?.second
|
||||||
|
@ -660,11 +652,9 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType)
|
||||||
fun ChatLayout(
|
fun ChatLayout(
|
||||||
remoteHostId: State<Long?>,
|
remoteHostId: State<Long?>,
|
||||||
chatInfo: State<ChatInfo?>,
|
chatInfo: State<ChatInfo?>,
|
||||||
reversedChatItems: State<List<ChatItem>>,
|
|
||||||
unreadCount: State<Int>,
|
unreadCount: State<Int>,
|
||||||
composeState: MutableState<ComposeState>,
|
composeState: MutableState<ComposeState>,
|
||||||
composeView: (@Composable () -> Unit),
|
composeView: (@Composable () -> Unit),
|
||||||
groupReports: State<GroupReports>,
|
|
||||||
scrollToItemId: MutableState<Long?>,
|
scrollToItemId: MutableState<Long?>,
|
||||||
attachmentOption: MutableState<AttachmentOption?>,
|
attachmentOption: MutableState<AttachmentOption?>,
|
||||||
attachmentBottomSheetState: ModalBottomSheetState,
|
attachmentBottomSheetState: ModalBottomSheetState,
|
||||||
|
@ -735,7 +725,8 @@ fun ChatLayout(
|
||||||
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
||||||
) {
|
) {
|
||||||
val composeViewHeight = remember { mutableStateOf(0.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 remoteHostId = remember { remoteHostId }.value
|
||||||
val chatInfo = remember { chatInfo }.value
|
val chatInfo = remember { chatInfo }.value
|
||||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||||
|
@ -748,8 +739,8 @@ fun ChatLayout(
|
||||||
override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float = 0f
|
override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float = 0f
|
||||||
}) {
|
}) {
|
||||||
ChatItemsList(
|
ChatItemsList(
|
||||||
remoteHostId, chatInfo, reversedChatItems, unreadCount, composeState, composeViewHeight, searchValue,
|
remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue,
|
||||||
useLinkPreviews, linkMode, groupReports, scrollToItemId, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages,
|
useLinkPreviews, linkMode, scrollToItemId, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages,
|
||||||
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
|
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
|
||||||
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
|
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
|
||||||
setReaction, showItemDetails, markItemsRead, markChatRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
|
setReaction, showItemDetails, markItemsRead, markChatRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
|
||||||
|
@ -757,7 +748,7 @@ fun ChatLayout(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (groupReports.value.reportsView) {
|
if (contentTag == MsgContentTag.Report) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.layoutId(CHAT_COMPOSE_LAYOUT_ID)
|
.layoutId(CHAT_COMPOSE_LAYOUT_ID)
|
||||||
|
@ -768,7 +759,7 @@ fun ChatLayout(
|
||||||
AnimatedVisibility(selectedChatItems.value != null) {
|
AnimatedVisibility(selectedChatItems.value != null) {
|
||||||
if (chatInfo != null) {
|
if (chatInfo != null) {
|
||||||
SelectedItemsBottomToolbar(
|
SelectedItemsBottomToolbar(
|
||||||
reversedChatItems = reversedChatItems,
|
contentTag = contentTag,
|
||||||
selectedChatItems = selectedChatItems,
|
selectedChatItems = selectedChatItems,
|
||||||
chatInfo = chatInfo,
|
chatInfo = chatInfo,
|
||||||
deleteItems = { _ ->
|
deleteItems = { _ ->
|
||||||
|
@ -805,23 +796,24 @@ fun ChatLayout(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val reportsCount = reportsCount(chatInfo?.id)
|
||||||
if (oneHandUI.value && chatBottomBar.value) {
|
if (oneHandUI.value && chatBottomBar.value) {
|
||||||
if (groupReports.value.showBar) {
|
if (contentTag == null && reportsCount > 0) {
|
||||||
ReportedCountToolbar(groupReports, withStatusBar = true, showGroupReports)
|
ReportedCountToolbar(reportsCount, withStatusBar = true, showGroupReports)
|
||||||
} else {
|
} else {
|
||||||
StatusBarBackground()
|
StatusBarBackground()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NavigationBarBackground(true, oneHandUI.value, noAlpha = true)
|
NavigationBarBackground(true, oneHandUI.value, noAlpha = true)
|
||||||
}
|
}
|
||||||
if (groupReports.value.reportsView) {
|
if (contentTag == MsgContentTag.Report) {
|
||||||
if (oneHandUI.value) {
|
if (oneHandUI.value) {
|
||||||
StatusBarBackground()
|
StatusBarBackground()
|
||||||
}
|
}
|
||||||
Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
|
Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
|
||||||
Box {
|
Box {
|
||||||
if (selectedChatItems.value == null) {
|
if (selectedChatItems.value == null) {
|
||||||
GroupReportsAppBar(groupReports, { ModalManager.end.closeModal() }, onSearchValueChanged)
|
GroupReportsAppBar(contentTag, { ModalManager.end.closeModal() }, onSearchValueChanged)
|
||||||
} else {
|
} else {
|
||||||
SelectedItemsTopToolbar(selectedChatItems, !oneHandUI.value)
|
SelectedItemsTopToolbar(selectedChatItems, !oneHandUI.value)
|
||||||
}
|
}
|
||||||
|
@ -832,14 +824,14 @@ fun ChatLayout(
|
||||||
Box {
|
Box {
|
||||||
if (selectedChatItems.value == null) {
|
if (selectedChatItems.value == null) {
|
||||||
if (chatInfo != 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 {
|
} else {
|
||||||
SelectedItemsTopToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value)
|
SelectedItemsTopToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (groupReports.value.showBar && (!oneHandUI.value || !chatBottomBar.value)) {
|
if (contentTag == null && reportsCount > 0 && (!oneHandUI.value || !chatBottomBar.value)) {
|
||||||
ReportedCountToolbar(groupReports, withStatusBar = false, showGroupReports)
|
ReportedCountToolbar(reportsCount, withStatusBar = false, showGroupReports)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -851,7 +843,7 @@ fun ChatLayout(
|
||||||
@Composable
|
@Composable
|
||||||
fun BoxScope.ChatInfoToolbar(
|
fun BoxScope.ChatInfoToolbar(
|
||||||
chatInfo: ChatInfo,
|
chatInfo: ChatInfo,
|
||||||
groupReports: State<GroupReports>,
|
contentTag: MsgContentTag?,
|
||||||
back: () -> Unit,
|
back: () -> Unit,
|
||||||
info: () -> Unit,
|
info: () -> Unit,
|
||||||
startCall: (CallMediaType) -> Unit,
|
startCall: (CallMediaType) -> Unit,
|
||||||
|
@ -873,7 +865,7 @@ fun BoxScope.ChatInfoToolbar(
|
||||||
showSearch.value = false
|
showSearch.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (appPlatform.isAndroid && !groupReports.value.reportsView) {
|
if (appPlatform.isAndroid && contentTag == null) {
|
||||||
BackHandler(onBack = onBackClicked)
|
BackHandler(onBack = onBackClicked)
|
||||||
}
|
}
|
||||||
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
|
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
|
||||||
|
@ -1069,7 +1061,7 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ReportedCountToolbar(
|
private fun ReportedCountToolbar(
|
||||||
groupReports: State<GroupReports>,
|
reportsCount: Int,
|
||||||
withStatusBar: Boolean,
|
withStatusBar: Boolean,
|
||||||
showGroupReports: () -> Unit
|
showGroupReports: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
@ -1087,12 +1079,11 @@ private fun ReportedCountToolbar(
|
||||||
) {
|
) {
|
||||||
Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.error)
|
Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.error)
|
||||||
Spacer(Modifier.width(4.dp))
|
Spacer(Modifier.width(4.dp))
|
||||||
val reports = groupReports.value.reportsCount
|
|
||||||
Text(
|
Text(
|
||||||
if (reports == 1) {
|
if (reportsCount == 1) {
|
||||||
stringResource(MR.strings.group_reports_active_one)
|
stringResource(MR.strings.group_reports_active_one)
|
||||||
} else {
|
} else {
|
||||||
stringResource(MR.strings.group_reports_active).format(reports)
|
stringResource(MR.strings.group_reports_active).format(reportsCount)
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.button
|
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)
|
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
|
private var reportsListState: LazyListState? = null
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BoxScope.ChatItemsList(
|
fun BoxScope.ChatItemsList(
|
||||||
remoteHostId: Long?,
|
remoteHostId: Long?,
|
||||||
chatInfo: ChatInfo,
|
chatInfo: ChatInfo,
|
||||||
reversedChatItems: State<List<ChatItem>>,
|
|
||||||
unreadCount: State<Int>,
|
unreadCount: State<Int>,
|
||||||
composeState: MutableState<ComposeState>,
|
composeState: MutableState<ComposeState>,
|
||||||
composeViewHeight: State<Dp>,
|
composeViewHeight: State<Dp>,
|
||||||
searchValue: State<String>,
|
searchValue: State<String>,
|
||||||
useLinkPreviews: Boolean,
|
useLinkPreviews: Boolean,
|
||||||
linkMode: SimplexLinkMode,
|
linkMode: SimplexLinkMode,
|
||||||
groupReports: State<GroupReports>,
|
|
||||||
scrollToItemId: MutableState<Long?>,
|
scrollToItemId: MutableState<Long?>,
|
||||||
selectedChatItems: MutableState<Set<Long>?>,
|
selectedChatItems: MutableState<Set<Long>?>,
|
||||||
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
|
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
|
||||||
|
@ -1151,8 +1140,17 @@ fun BoxScope.ChatItemsList(
|
||||||
) {
|
) {
|
||||||
val searchValueIsEmpty = remember { derivedStateOf { searchValue.value.isEmpty() } }
|
val searchValueIsEmpty = remember { derivedStateOf { searchValue.value.isEmpty() } }
|
||||||
val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf<Long>()) }
|
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 contentTag = LocalContentTag.current
|
||||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar).roundToPx() })
|
// 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
|
/** 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
|
* [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)
|
LazyListState(index + 1, -maxHeightForList.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
SaveReportsStateOnDispose(groupReports, listState)
|
SaveReportsStateOnDispose(listState)
|
||||||
val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } }
|
val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } }
|
||||||
val loadingMoreItems = remember { mutableStateOf(false) }
|
val loadingMoreItems = remember { mutableStateOf(false) }
|
||||||
val animatedScrollingInProgress = remember { mutableStateOf(false) }
|
val animatedScrollingInProgress = remember { mutableStateOf(false) }
|
||||||
|
@ -1181,7 +1179,7 @@ fun BoxScope.ChatItemsList(
|
||||||
ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect)
|
ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect)
|
||||||
}
|
}
|
||||||
if (!loadingMoreItems.value) {
|
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
|
if (loadingMoreItems.value) return@PreloadItems false
|
||||||
try {
|
try {
|
||||||
loadingMoreItems.value = true
|
loadingMoreItems.value = true
|
||||||
|
@ -1201,11 +1199,11 @@ fun BoxScope.ChatItemsList(
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val scrollToItem: (Long) -> Unit = remember {
|
val scrollToItem: (Long) -> Unit = remember {
|
||||||
// In group reports just set the itemId to scroll to so the main ChatView will handle scrolling
|
// 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)
|
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) }
|
val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem, contentTag) }
|
||||||
if (!groupReports.value.reportsView) {
|
if (contentTag == null) {
|
||||||
LaunchedEffect(Unit) { snapshotFlow { scrollToItemId.value }.filterNotNull().collect {
|
LaunchedEffect(Unit) { snapshotFlow { scrollToItemId.value }.filterNotNull().collect {
|
||||||
if (appPlatform.isAndroid) {
|
if (appPlatform.isAndroid) {
|
||||||
ModalManager.end.closeModals()
|
ModalManager.end.closeModals()
|
||||||
|
@ -1214,18 +1212,18 @@ fun BoxScope.ChatItemsList(
|
||||||
scrollToItemId.value = null }
|
scrollToItemId.value = null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoadLastItems(loadingMoreItems, remoteHostId, chatInfo, groupReports)
|
LoadLastItems(loadingMoreItems, remoteHostId, chatInfo)
|
||||||
SmallScrollOnNewMessage(listState, reversedChatItems)
|
SmallScrollOnNewMessage(listState, reversedChatItems)
|
||||||
val finishedInitialComposition = remember { mutableStateOf(false) }
|
val finishedInitialComposition = remember { mutableStateOf(false) }
|
||||||
NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed)
|
NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed)
|
||||||
|
|
||||||
DisposableEffectOnGone(
|
DisposableEffectOnGone(
|
||||||
always = {
|
always = {
|
||||||
chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(groupReports.value.contentTag)), groupReports.value.contentTag)
|
chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(contentTag)), contentTag)
|
||||||
},
|
},
|
||||||
whenGone = {
|
whenGone = {
|
||||||
VideoPlayerHolder.releaseAll()
|
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),
|
Modifier.align(Alignment.BottomCenter),
|
||||||
state = listState.value,
|
state = listState.value,
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
top = topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar),
|
top = topPaddingToContent,
|
||||||
bottom = composeViewHeight.value
|
bottom = composeViewHeight.value
|
||||||
),
|
),
|
||||||
reverseLayout = true,
|
reverseLayout = true,
|
||||||
additionalBarOffset = composeViewHeight,
|
additionalBarOffset = composeViewHeight,
|
||||||
additionalTopBar = remember { derivedStateOf { groupReports.value.showBar } },
|
additionalTopBar = rememberUpdatedState(contentTag == null && reportsCount > 0),
|
||||||
chatBottomBar = remember { appPrefs.chatBottomBar.state }
|
chatBottomBar = remember { appPrefs.chatBottomBar.state }
|
||||||
) {
|
) {
|
||||||
val mergedItemsValue = mergedItems.value
|
val mergedItemsValue = mergedItems.value
|
||||||
|
@ -1522,8 +1520,8 @@ fun BoxScope.ChatItemsList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FloatingButtons(loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, groupReports, markChatRead, listState)
|
FloatingButtons(topPaddingToContent, topPaddingToContentPx, loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState)
|
||||||
FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar)).align(Alignment.TopCenter), mergedItems, listState, groupReports)
|
FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
snapshotFlow { listState.value.isScrollInProgress }
|
snapshotFlow { listState.value.isScrollInProgress }
|
||||||
|
@ -1543,14 +1541,15 @@ fun BoxScope.ChatItemsList(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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) {
|
LaunchedEffect(remoteHostId, chatInfo.id) {
|
||||||
try {
|
try {
|
||||||
loadingMoreItems.value = true
|
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)
|
delay(500)
|
||||||
withContext(Dispatchers.Default) {
|
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 {
|
} finally {
|
||||||
loadingMoreItems.value = false
|
loadingMoreItems.value = false
|
||||||
|
@ -1616,6 +1615,8 @@ private fun NotifyChatListOnFinishingComposition(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BoxScope.FloatingButtons(
|
fun BoxScope.FloatingButtons(
|
||||||
|
topPaddingToContent: Dp,
|
||||||
|
topPaddingToContentPx: State<Int>,
|
||||||
loadingMoreItems: MutableState<Boolean>,
|
loadingMoreItems: MutableState<Boolean>,
|
||||||
animatedScrollingInProgress: MutableState<Boolean>,
|
animatedScrollingInProgress: MutableState<Boolean>,
|
||||||
mergedItems: State<MergedItems>,
|
mergedItems: State<MergedItems>,
|
||||||
|
@ -1623,12 +1624,10 @@ fun BoxScope.FloatingButtons(
|
||||||
maxHeight: State<Int>,
|
maxHeight: State<Int>,
|
||||||
composeViewHeight: State<Dp>,
|
composeViewHeight: State<Dp>,
|
||||||
searchValue: State<String>,
|
searchValue: State<String>,
|
||||||
groupReports: State<GroupReports>,
|
|
||||||
markChatRead: () -> Unit,
|
markChatRead: () -> Unit,
|
||||||
listState: State<LazyListState>
|
listState: State<LazyListState>
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar).roundToPx() })
|
|
||||||
val bottomUnreadCount = remember {
|
val bottomUnreadCount = remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (unreadCount.value == 0) return@derivedStateOf 0
|
if (unreadCount.value == 0) return@derivedStateOf 0
|
||||||
|
@ -1674,7 +1673,7 @@ fun BoxScope.FloatingButtons(
|
||||||
val showDropDown = remember { mutableStateOf(false) }
|
val showDropDown = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
TopEndFloatingButton(
|
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,
|
topUnreadCount,
|
||||||
animatedScrollingInProgress,
|
animatedScrollingInProgress,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
@ -1696,7 +1695,7 @@ fun BoxScope.FloatingButtons(
|
||||||
DefaultDropdownMenu(
|
DefaultDropdownMenu(
|
||||||
showDropDown,
|
showDropDown,
|
||||||
modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) } },
|
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(
|
ItemAction(
|
||||||
generalGetString(MR.strings.mark_read),
|
generalGetString(MR.strings.mark_read),
|
||||||
|
@ -1713,7 +1712,7 @@ fun BoxScope.FloatingButtons(
|
||||||
fun PreloadItems(
|
fun PreloadItems(
|
||||||
chatId: String,
|
chatId: String,
|
||||||
ignoreLoadingRequests: MutableSet<Long>,
|
ignoreLoadingRequests: MutableSet<Long>,
|
||||||
reversedChatItems: State<List<ChatItem>>,
|
contentTag: MsgContentTag?,
|
||||||
mergedItems: State<MergedItems>,
|
mergedItems: State<MergedItems>,
|
||||||
listState: State<LazyListState>,
|
listState: State<LazyListState>,
|
||||||
remaining: Int,
|
remaining: Int,
|
||||||
|
@ -1724,8 +1723,8 @@ fun PreloadItems(
|
||||||
val chatId = rememberUpdatedState(chatId)
|
val chatId = rememberUpdatedState(chatId)
|
||||||
val loadItems = rememberUpdatedState(loadItems)
|
val loadItems = rememberUpdatedState(loadItems)
|
||||||
val ignoreLoadingRequests = rememberUpdatedState(ignoreLoadingRequests)
|
val ignoreLoadingRequests = rememberUpdatedState(ignoreLoadingRequests)
|
||||||
PreloadItemsBefore(allowLoad, chatId, ignoreLoadingRequests, reversedChatItems, mergedItems, listState, remaining, loadItems)
|
PreloadItemsBefore(allowLoad, chatId, ignoreLoadingRequests, contentTag, mergedItems, listState, remaining, loadItems)
|
||||||
PreloadItemsAfter(allowLoad, chatId, reversedChatItems, mergedItems, listState, remaining, loadItems)
|
PreloadItemsAfter(allowLoad, chatId, contentTag, mergedItems, listState, remaining, loadItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -1733,7 +1732,7 @@ private fun PreloadItemsBefore(
|
||||||
allowLoad: State<Boolean>,
|
allowLoad: State<Boolean>,
|
||||||
chatId: State<String>,
|
chatId: State<String>,
|
||||||
ignoreLoadingRequests: State<MutableSet<Long>>,
|
ignoreLoadingRequests: State<MutableSet<Long>>,
|
||||||
reversedChatItems: State<List<ChatItem>>,
|
contentTag: MsgContentTag?,
|
||||||
mergedItems: State<MergedItems>,
|
mergedItems: State<MergedItems>,
|
||||||
listState: State<LazyListState>,
|
listState: State<LazyListState>,
|
||||||
remaining: Int,
|
remaining: Int,
|
||||||
|
@ -1746,12 +1745,12 @@ private fun PreloadItemsBefore(
|
||||||
val splits = mergedItems.value.splits
|
val splits = mergedItems.value.splits
|
||||||
val lastVisibleIndex = (listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0)
|
val lastVisibleIndex = (listState.value.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0)
|
||||||
var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits)
|
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) {
|
if (splits.isEmpty() && items.isNotEmpty() && lastVisibleIndex > mergedItems.value.items.size - remaining) {
|
||||||
lastIndexToLoadFrom = 0
|
lastIndexToLoadFrom = items.lastIndex
|
||||||
}
|
}
|
||||||
if (allowLoad.value && lastIndexToLoadFrom != null) {
|
if (allowLoad.value && lastIndexToLoadFrom != null) {
|
||||||
items.getOrNull(items.lastIndex - lastIndexToLoadFrom)?.id
|
items.getOrNull(lastIndexToLoadFrom)?.id
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -1760,10 +1759,12 @@ private fun PreloadItemsBefore(
|
||||||
.filter { !ignoreLoadingRequests.value.contains(it) }
|
.filter { !ignoreLoadingRequests.value.contains(it) }
|
||||||
.collect { loadFromItemId ->
|
.collect { loadFromItemId ->
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val sizeWas = reversedChatItems.value.size
|
val items = reversedChatItemsStatic(contentTag)
|
||||||
val oldestItemIdWas = reversedChatItems.value.lastOrNull()?.id
|
val sizeWas = items.size
|
||||||
|
val oldestItemIdWas = items.lastOrNull()?.id
|
||||||
val triedToLoad = loadItems.value(chatId.value, ChatPagination.Before(loadFromItemId, ChatPagination.PRELOAD_COUNT))
|
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)
|
ignoreLoadingRequests.value.add(loadFromItemId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1775,7 +1776,7 @@ private fun PreloadItemsBefore(
|
||||||
private fun PreloadItemsAfter(
|
private fun PreloadItemsAfter(
|
||||||
allowLoad: MutableState<Boolean>,
|
allowLoad: MutableState<Boolean>,
|
||||||
chatId: State<String>,
|
chatId: State<String>,
|
||||||
reversedChatItems: State<List<ChatItem>>,
|
contentTag: MsgContentTag?,
|
||||||
mergedItems: State<MergedItems>,
|
mergedItems: State<MergedItems>,
|
||||||
listState: State<LazyListState>,
|
listState: State<LazyListState>,
|
||||||
remaining: Int,
|
remaining: Int,
|
||||||
|
@ -1796,7 +1797,7 @@ private fun PreloadItemsAfter(
|
||||||
snapshotFlow { listState.value.firstVisibleItemIndex }
|
snapshotFlow { listState.value.firstVisibleItemIndex }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.map { firstVisibleIndex ->
|
.map { firstVisibleIndex ->
|
||||||
val items = reversedChatItems.value
|
val items = reversedChatItemsStatic(contentTag)
|
||||||
val splits = mergedItems.value.splits
|
val splits = mergedItems.value.splits
|
||||||
val split = splits.lastOrNull { it.indexRangeInParentItems.contains(firstVisibleIndex) }
|
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)
|
// 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
|
@Composable
|
||||||
private fun FloatingDate(
|
private fun FloatingDate(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
|
topPaddingToContentPx: State<Int>,
|
||||||
mergedItems: State<MergedItems>,
|
mergedItems: State<MergedItems>,
|
||||||
listState: State<LazyListState>,
|
listState: State<LazyListState>,
|
||||||
groupReports: State<GroupReports>
|
|
||||||
) {
|
) {
|
||||||
val isNearBottom = remember(chatModel.chatId) { mutableStateOf(listState.value.firstVisibleItemIndex == 0) }
|
val isNearBottom = remember(chatModel.chatId) { mutableStateOf(listState.value.firstVisibleItemIndex == 0) }
|
||||||
val nearBottomIndex = remember(chatModel.chatId) { mutableStateOf(if (isNearBottom.value) -1 else 0) }
|
val nearBottomIndex = remember(chatModel.chatId) { mutableStateOf(if (isNearBottom.value) -1 else 0) }
|
||||||
val showDate = remember(chatModel.chatId) { mutableStateOf(false) }
|
val showDate = remember(chatModel.chatId) { mutableStateOf(false) }
|
||||||
val density = LocalDensity.current.density
|
val density = LocalDensity.current.density
|
||||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar).roundToPx() })
|
|
||||||
val fontSizeSqrtMultiplier = fontSizeSqrtMultiplier
|
val fontSizeSqrtMultiplier = fontSizeSqrtMultiplier
|
||||||
val lastVisibleItemDate = remember {
|
val lastVisibleItemDate = remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
|
@ -1957,10 +1957,11 @@ private fun FloatingDate(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SaveReportsStateOnDispose(groupReports: State<GroupReports>, listState: State<LazyListState>) {
|
private fun SaveReportsStateOnDispose(listState: State<LazyListState>) {
|
||||||
|
val contentTag = LocalContentTag.current
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
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? {
|
private fun oldestPartiallyVisibleListItemInListStateOrNull(topPaddingToContentPx: State<Int>, mergedItems: State<MergedItems>, listState: State<LazyListState>): ListItem? {
|
||||||
val lastFullyVisibleOffset = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value
|
val lastFullyVisibleOffset = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value
|
||||||
return mergedItems.value.items.getOrNull((listState.value.layoutInfo.visibleItemsInfo.lastOrNull { item ->
|
return mergedItems.value.items.getOrNull((listState.value.layoutInfo.visibleItemsInfo.lastOrNull { item ->
|
||||||
|
@ -2665,11 +2678,9 @@ fun PreviewChatLayout() {
|
||||||
ChatLayout(
|
ChatLayout(
|
||||||
remoteHostId = remember { mutableStateOf(null) },
|
remoteHostId = remember { mutableStateOf(null) },
|
||||||
chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) },
|
chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) },
|
||||||
reversedChatItems = remember { mutableStateOf(emptyList()) },
|
|
||||||
unreadCount = unreadCount,
|
unreadCount = unreadCount,
|
||||||
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
||||||
composeView = {},
|
composeView = {},
|
||||||
groupReports = remember { mutableStateOf(GroupReports(0, false)) },
|
|
||||||
scrollToItemId = remember { mutableStateOf(null) },
|
scrollToItemId = remember { mutableStateOf(null) },
|
||||||
attachmentOption = remember { mutableStateOf<AttachmentOption?>(null) },
|
attachmentOption = remember { mutableStateOf<AttachmentOption?>(null) },
|
||||||
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
|
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
|
||||||
|
@ -2742,11 +2753,9 @@ fun PreviewGroupChatLayout() {
|
||||||
ChatLayout(
|
ChatLayout(
|
||||||
remoteHostId = remember { mutableStateOf(null) },
|
remoteHostId = remember { mutableStateOf(null) },
|
||||||
chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) },
|
chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) },
|
||||||
reversedChatItems = remember { mutableStateOf(emptyList()) },
|
|
||||||
unreadCount = unreadCount,
|
unreadCount = unreadCount,
|
||||||
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
||||||
composeView = {},
|
composeView = {},
|
||||||
groupReports = remember { mutableStateOf(GroupReports(0, false)) },
|
|
||||||
scrollToItemId = remember { mutableStateOf(null) },
|
scrollToItemId = remember { mutableStateOf(null) },
|
||||||
attachmentOption = remember { mutableStateOf<AttachmentOption?>(null) },
|
attachmentOption = remember { mutableStateOf<AttachmentOption?>(null) },
|
||||||
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
|
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
|
||||||
|
|
|
@ -48,7 +48,7 @@ fun BoxScope.SelectedItemsTopToolbar(selectedChatItems: MutableState<Set<Long>?>
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectedItemsBottomToolbar(
|
fun SelectedItemsBottomToolbar(
|
||||||
chatInfo: ChatInfo,
|
chatInfo: ChatInfo,
|
||||||
reversedChatItems: State<List<ChatItem>>,
|
contentTag: MsgContentTag?,
|
||||||
selectedChatItems: MutableState<Set<Long>?>,
|
selectedChatItems: MutableState<Set<Long>?>,
|
||||||
deleteItems: (Boolean) -> Unit, // Boolean - delete for everyone is possible
|
deleteItems: (Boolean) -> Unit, // Boolean - delete for everyone is possible
|
||||||
moderateItems: () -> Unit,
|
moderateItems: () -> Unit,
|
||||||
|
@ -107,8 +107,9 @@ fun SelectedItemsBottomToolbar(
|
||||||
}
|
}
|
||||||
Divider(Modifier.align(Alignment.TopStart))
|
Divider(Modifier.align(Alignment.TopStart))
|
||||||
}
|
}
|
||||||
LaunchedEffect(chatInfo, reversedChatItems.value, selectedChatItems.value) {
|
val chatItems = remember { derivedStateOf { chatModel.chatItemsForContent(contentTag).value } }
|
||||||
recheckItems(chatInfo, reversedChatItems.value.asReversed(), selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited)
|
LaunchedEffect(chatInfo, chatItems.value, selectedChatItems.value) {
|
||||||
|
recheckItems(chatInfo, chatItems.value, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,28 +16,14 @@ import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
val LocalContentTag: ProvidableCompositionLocal<MsgContentTag?> = staticCompositionLocalOf { null }
|
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
|
@Composable
|
||||||
private fun GroupReportsView(staleChatId: State<String?>, scrollToItemId: MutableState<Long?>) {
|
private fun GroupReportsView(staleChatId: State<String?>, scrollToItemId: MutableState<Long?>) {
|
||||||
ChatView(staleChatId, reportsView = true, scrollToItemId, onComposed = {})
|
ChatView(staleChatId, contentTag = MsgContentTag.Report, scrollToItemId, onComposed = {})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GroupReportsAppBar(
|
fun GroupReportsAppBar(
|
||||||
groupReports: State<GroupReports>,
|
contentTag: MsgContentTag?,
|
||||||
close: () -> Unit,
|
close: () -> Unit,
|
||||||
onSearchValueChanged: (String) -> Unit
|
onSearchValueChanged: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
|
@ -65,11 +51,11 @@ fun GroupReportsAppBar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
ItemsReload(groupReports)
|
ItemsReload(contentTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ItemsReload(groupReports: State<GroupReports>) {
|
private fun ItemsReload(contentTag: MsgContentTag?) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
snapshotFlow { chatModel.chatId.value }
|
snapshotFlow { chatModel.chatId.value }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
@ -79,7 +65,7 @@ private fun ItemsReload(groupReports: State<GroupReports>) {
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.filter { it.chatInfo is ChatInfo.Group }
|
.filter { it.chatInfo is ChatInfo.Group }
|
||||||
.collect { chat ->
|
.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>) {
|
private suspend fun reloadItems(chat: Chat, contentTag: MsgContentTag?) {
|
||||||
val contentFilter = groupReports.value.toContentTag()
|
apiLoadMessages(chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId, contentTag, ChatPagination.Initial(ChatPagination.INITIAL_COUNT))
|
||||||
apiLoadMessages(chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId, contentFilter, ChatPagination.Initial(ChatPagination.INITIAL_COUNT))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ private fun MergedMarkedDeletedText(chatItem: ChatItem, chatInfo: ChatInfo, reve
|
||||||
|
|
||||||
fun markedDeletedText(cItem: ChatItem, chatInfo: ChatInfo): String =
|
fun markedDeletedText(cItem: ChatItem, chatInfo: ChatInfo): String =
|
||||||
if (cItem.meta.itemDeleted != null && cItem.isReport) {
|
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)
|
generalGetString(MR.strings.report_item_archived_by).format(cItem.meta.itemDeleted.byGroupMember.displayName)
|
||||||
} else {
|
} else {
|
||||||
generalGetString(MR.strings.report_item_archived)
|
generalGetString(MR.strings.report_item_archived)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue