This commit is contained in:
Avently
2025-01-08 17:01:40 +07:00
parent fffab507be
commit 443f35831b
20 changed files with 173 additions and 65 deletions
@@ -81,6 +81,9 @@ actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
chatModel.chatId.value = null
chatItems.clearAndNotify()
}
withChats {
chatItems.clearAndNotify()
}
}
} else {
// ChatList, nothing to do. Maybe to show other view except ChatList
@@ -66,13 +66,12 @@ object ChatModel {
val chatId = mutableStateOf<String?>(null)
val chatsContext = ChatsContext(null)
val reportsChatsContext = ChatsContext(MsgContentTag.Report)
// declaration of chatsContext should be before any other variable that is taken from ChatsContext class and used in the model, otherwise, strange crash with NullPointerException for "this" parameter in random functions
val chats: State<List<Chat>> = chatsContext.chats
/** if you modify the items by adding/removing them, use helpers methods like [addAndNotify], [removeLastAndNotify], [removeAllAndNotify], [clearAndNotify] and so on.
* If some helper is missing, create it. Notify is needed to track state of items that we added manually (not via api call). See [apiLoadMessages].
* If you use api call to get the items, use just [add] instead of [addAndNotify].
* Never modify underlying list directly because it produces unexpected results in ChatView's LazyColumn (setting by index is ok) */
// declaration of chatsContext should be after any other variable that is directly attached to ChatsContext class, otherwise, strange crash with NullPointerException for "this" parameter in random functions
val chatItems: State<SnapshotStateList<ChatItem>> = chatsContext.chatItems
// rhId, chatId
val deletedChats = mutableStateOf<List<Pair<Long?, String>>>(emptyList())
val chatItemStatuses = mutableMapOf<Long, CIStatus>()
@@ -173,7 +172,7 @@ object ChatModel {
// return true if you handled the click
var centerPanelBackgroundClickHandler: (() -> Boolean)? = null
fun chatItemsForContent(contentTag: MsgContentTag?): State<List<ChatItem>> = when(contentTag) {
fun chatItemsForContent(contentTag: MsgContentTag?): State<SnapshotStateList<ChatItem>> = when(contentTag) {
null -> chatsContext.chatItems
MsgContentTag.Report -> reportsChatsContext.chatItems
else -> TODO()
@@ -619,9 +618,9 @@ object ChatModel {
}
}
val popChatCollector = PopChatCollector()
val popChatCollector = PopChatCollector(contentTag)
class PopChatCollector {
class PopChatCollector(contentTag: MsgContentTag?) {
private val subject = MutableSharedFlow<Unit>()
private var remoteHostId: Long? = null
private val chatsToPop = mutableMapOf<ChatId, Instant>()
@@ -631,7 +630,7 @@ object ChatModel {
subject
.throttleLatest(2000)
.collect {
withChats {
withChats(contentTag) {
chats.replaceAll(popCollectedChats())
}
}
@@ -671,7 +670,7 @@ object ChatModel {
fun markChatItemsRead(remoteHostId: Long?, chatInfo: ChatInfo, itemIds: List<Long>? = null) {
val cInfo = chatInfo
val markedRead = markItemsReadInCurrentChat(chatInfo, itemIds)
val markedRead = markItemsReadInCurrentChat(chatInfo, contentTag, itemIds)
// update preview
val chatIdx = getChatIndex(remoteHostId, cInfo.id)
if (chatIdx >= 0) {
@@ -806,7 +805,7 @@ object ChatModel {
}
fun removeLiveDummy() {
if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
if (chatItemsForContent(null).value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
withApi {
withChats {
chatItems.removeLastAndNotify()
@@ -815,11 +814,11 @@ object ChatModel {
}
}
private fun markItemsReadInCurrentChat(chatInfo: ChatInfo, itemIds: List<Long>? = null): Int {
private fun markItemsReadInCurrentChat(chatInfo: ChatInfo, contentTag: MsgContentTag?, itemIds: List<Long>? = null): Int {
val cInfo = chatInfo
var markedRead = 0
if (chatId.value == cInfo.id) {
val items = chatItems.value
val items = chatItemsForContent(contentTag).value
var i = items.lastIndex
val itemIdsFromRange = itemIds?.toMutableSet() ?: mutableSetOf()
val markedReadIds = mutableSetOf<Long>()
@@ -864,19 +863,17 @@ object ChatModel {
}
}
fun getChatItemIndexOrNull(cItem: ChatItem): Int? {
val reversedChatItems = chatItems.asReversed()
fun getChatItemIndexOrNull(cItem: ChatItem, reversedChatItems: List<ChatItem>): Int? {
val index = reversedChatItems.indexOfFirst { it.id == cItem.id }
return if (index != -1) index else null
}
// this function analyses "connected" events and assumes that each member will be there only once
fun getConnectedMemberNames(cItem: ChatItem): Pair<Int, List<String>> {
fun getConnectedMemberNames(cItem: ChatItem, reversedChatItems: List<ChatItem>): Pair<Int, List<String>> {
var count = 0
val ns = mutableListOf<String>()
var idx = getChatItemIndexOrNull(cItem)
var idx = getChatItemIndexOrNull(cItem, reversedChatItems)
if (cItem.mergeCategory != null && idx != null) {
val reversedChatItems = chatItems.asReversed()
while (idx < reversedChatItems.size) {
val ci = reversedChatItems[idx]
if (ci.mergeCategory != cItem.mergeCategory) break
@@ -893,9 +890,8 @@ object ChatModel {
// returns the index of the first item in the same merged group (the first hidden item)
// and the previous visible item with another merge category
fun getPrevShownChatItem(ciIndex: Int?, ciCategory: CIMergeCategory?): Pair<Int?, ChatItem?> {
fun getPrevShownChatItem(ciIndex: Int?, ciCategory: CIMergeCategory?, reversedChatItems: List<ChatItem>): Pair<Int?, ChatItem?> {
var i = ciIndex ?: return null to null
val reversedChatItems = chatItems.asReversed()
val fst = reversedChatItems.lastIndex
while (i < fst) {
i++
@@ -908,8 +904,7 @@ object ChatModel {
}
// returns the previous member in the same merge group and the count of members in this group
fun getPrevHiddenMember(member: GroupMember, range: IntRange): Pair<GroupMember?, Int> {
val reversedChatItems = chatItems.asReversed()
fun getPrevHiddenMember(member: GroupMember, range: IntRange, reversedChatItems: List<ChatItem>): Pair<GroupMember?, Int> {
var prevMember: GroupMember? = null
val names: MutableSet<Long> = mutableSetOf()
for (i in range) {
@@ -3052,6 +3052,9 @@ object ChatController {
withChats {
updateGroup(rh, groupInfo)
}
withReportsChatsIfOpen {
updateGroup(rh, groupInfo)
}
}
}
@@ -56,7 +56,7 @@ private fun sendCommand(chatModel: ChatModel, composeState: MutableState<Compose
// TODO show active remote host in chat console?
chatModel.controller.sendCmd(chatModel.remoteHostId(), CC.Console(s))
// LALAL
// for (i in 201..210) {
// for (i in 221..240) {
// chatModel.controller.sendCmd(chatModel.remoteHostId(), CC.Console("/_report #5 5965 reason=community $i"))
// }
composeState.value = ComposeState(useLinkPreviews = false)
@@ -36,6 +36,7 @@ import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
@@ -1250,6 +1251,11 @@ suspend fun save(applyToMode: DefaultThemeMode?, newTheme: ThemeModeOverride?, c
updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes)))
}
}
withReportsChatsIfOpen {
if (chat.chatInfo is ChatInfo.Group) {
updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes)))
}
}
}
}
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.*
import chat.simplex.common.model.*
import chat.simplex.common.platform.chatModel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
@@ -240,14 +239,13 @@ data class ActiveChatState (
}
}
fun visibleItemIndexesNonReversed(mergedItems: State<MergedItems>, listState: LazyListState): IntRange {
fun visibleItemIndexesNonReversed(mergedItems: State<MergedItems>, reversedItemsSize: Int, listState: LazyListState): IntRange {
val zero = 0 .. 0
if (listState.layoutInfo.totalItemsCount == 0) return zero
val newest = mergedItems.value.items.getOrNull(listState.firstVisibleItemIndex)?.startIndexInReversedItems
val oldest = mergedItems.value.items.getOrNull(listState.layoutInfo.visibleItemsInfo.last().index)?.lastIndexInReversed()
if (newest == null || oldest == null) return zero
val size = chatModel.chatItems.value.size
val range = size - oldest .. size - newest
val range = reversedItemsSize - oldest .. reversedItemsSize - newest
if (range.first < 0 || range.last < 0) return zero
// visible items mapped to their underlying data structure which is chatModel.chatItems
@@ -77,7 +77,7 @@ fun ChatView(
ModalManager.end.closeModals()
}
} else {
val showArchivedReports = remember { mutableStateOf(false) }
val showArchivedReports = remember { mutableStateOf(if (reportsView) reportsShowArchived else false) }
val groupReports = remember { derivedStateOf {
val reportsCount = if (activeChatInfo.value is ChatInfo.Group) activeChatStats.value?.reportsCount ?: 0 else 0
GroupReports(reportsCount, reportsView, showArchivedReports.value) }
@@ -125,7 +125,10 @@ fun ChatView(
}
}
val clipboard = LocalClipboardManager.current
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false)) {
CompositionLocalProvider(
LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false),
LocalContentTag provides groupReports.value.contentTag
) {
when (chatInfo) {
is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> {
var groupMembersJob: Job = remember { Job() }
@@ -511,6 +514,11 @@ fun ChatView(
withChats {
updateChatItem(cInfo, updatedCI)
}
withReportsChatsIfOpen {
if (cItem.content.msgContent is MsgContent.MCReport) {
updateChatItem(cInfo, updatedCI)
}
}
}
}
},
@@ -528,7 +536,9 @@ fun ChatView(
groupMembersJob.cancel()
groupMembersJob = scope.launch(Dispatchers.Default) {
var initialCiInfo = loadChatItemInfo() ?: return@launch
ModalManager.end.closeModals()
if (!ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) {
ModalManager.end.closeModals()
}
ModalManager.end.showModalCloseable(endButtons = {
ShareButton {
clipboard.shareText(itemInfoShareText(chatModel, cItem, initialCiInfo, chatModel.controller.appPrefs.developerTools.get()))
@@ -1078,6 +1088,10 @@ 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 */
private var reportsListState: LazyListState? = null
private var reportsShowArchived: Boolean = false
@Composable
fun BoxScope.ChatItemsList(
remoteHostId: Long?,
@@ -1130,12 +1144,18 @@ fun BoxScope.ChatItemsList(
)
val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, saver = LazyListState.Saver) {
val index = mergedItems.value.items.indexOfLast { it.hasUnread() }
if (index <= 0) {
val reportsState = reportsListState
if (reportsState != null) {
reportsListState = null
reportsShowArchived = false
reportsState
} else if (index <= 0) {
LazyListState(0, 0)
} else {
LazyListState(index + 1, -maxHeightForList.value)
}
})
SaveReportsStateOnDispose(groupReports, listState)
val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } }
val loadingMoreItems = remember { mutableStateOf(false) }
val animatedScrollingInProgress = remember { mutableStateOf(false) }
@@ -1146,7 +1166,7 @@ fun BoxScope.ChatItemsList(
try {
loadingMoreItems.value = true
loadMessages(chatId, pagination) {
visibleItemIndexesNonReversed(mergedItems, listState.value)
visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value)
}
} finally {
loadingMoreItems.value = false
@@ -1304,7 +1324,7 @@ fun BoxScope.ChatItemsList(
val rangeValue = range.value
val (prevMember, memCount) =
if (rangeValue != null) {
chatModel.getPrevHiddenMember(member, rangeValue)
chatModel.getPrevHiddenMember(member, rangeValue, reversedChatItems.value)
} else {
null to 1
}
@@ -1702,7 +1722,7 @@ private fun PreloadItemsBefore(
var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits)
val items = reversedChatItems.value
if (splits.isEmpty() && items.isNotEmpty() && lastVisibleIndex > mergedItems.value.items.size - remaining && items.size >= ChatPagination.INITIAL_COUNT) {
lastIndexToLoadFrom = items.lastIndex
lastIndexToLoadFrom = 0
}
if (allowLoad.value && lastIndexToLoadFrom != null) {
items.getOrNull(items.lastIndex - lastIndexToLoadFrom)?.id
@@ -1910,6 +1930,16 @@ private fun FloatingDate(
}
}
@Composable
private fun SaveReportsStateOnDispose(groupReports: State<GroupReports>, listState: State<LazyListState>) {
DisposableEffect(Unit) {
onDispose {
reportsListState = if (groupReports.value.reportsView && ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) listState.value else null
reportsShowArchived = if (groupReports.value.reportsView && ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) groupReports.value.showArchived else false
}
}
}
@Composable
private fun DownloadFilesButton(
forwardConfirmation: ForwardConfirmation.FilesNotAccepted,
@@ -2051,7 +2081,7 @@ private fun scrollToItem(
val oldSize = reversedChatItems.value.size
withContext(Dispatchers.Default) {
loadMessages(chatInfo.value.id, pagination) {
visibleItemIndexesNonReversed(mergedItems, listState.value)
visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value)
}
}
var repeatsLeft = 50
@@ -2199,10 +2229,10 @@ private fun selectUnselectChatItem(
) {
val itemIds = mutableSetOf<Long>()
if (!revealed.value) {
val currIndex = chatModel.getChatItemIndexOrNull(ci)
val currIndex = chatModel.getChatItemIndexOrNull(ci, reversedChatItems.value)
val ciCategory = ci.mergeCategory
if (currIndex != null && ciCategory != null) {
val (prevHidden, _) = chatModel.getPrevShownChatItem(currIndex, ciCategory)
val (prevHidden, _) = chatModel.getPrevShownChatItem(currIndex, ciCategory, reversedChatItems.value)
val range = chatViewItemsRange(currIndex, prevHidden)
if (range != null) {
val reversed = reversedChatItems.value
@@ -2251,7 +2281,9 @@ private fun deleteMessages(chatRh: Long?, chatInfo: ChatInfo, itemIds: List<Long
for (di in deleted) {
val toChatItem = di.toChatItem?.chatItem
if (toChatItem != null) {
upsertChatItem(chatRh, chatInfo, toChatItem)
if (toChatItem.content.msgContent is MsgContent.MCReport) {
upsertChatItem(chatRh, chatInfo, toChatItem)
}
} else {
removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
}
@@ -2291,7 +2323,6 @@ private fun markUnreadChatAsRead(chatId: String) {
}
withReportsChatsIfOpen {
replaceChat(chatRh, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
markChatTagRead(chat)
}
}
}
@@ -2470,7 +2501,7 @@ private fun ViewConfiguration.bigTouchSlop(slop: Float = 50f) = object: ViewConf
private fun forwardContent(chatItemsIds: List<Long>, chatInfo: ChatInfo) {
chatModel.chatId.value = null
chatModel.sharedContent.value = SharedContent.Forward(
chatModel.chatItems.value.filter { chatItemsIds.contains(it.id) },
chatModel.chatItemsForContent(null).value.filter { chatItemsIds.contains(it.id) },
chatInfo
)
}
@@ -798,7 +798,7 @@ fun ComposeView(
fun editPrevMessage() {
if (composeState.value.contextItem != ComposeContextItem.NoContextItem || composeState.value.preview != ComposePreview.NoPreview) return
val lastEditable = chatModel.chatItems.value.findLast { it.meta.editable }
val lastEditable = chatModel.chatItemsForContent(null).value.findLast { it.meta.editable }
if (lastEditable != null) {
composeState.value = ComposeState(editingItem = lastEditable, useLinkPreviews = useLinkPreviews)
}
@@ -26,6 +26,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.ChatInfoToolbarTitle
import chat.simplex.common.views.helpers.*
@@ -64,6 +65,9 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea
withChats {
upsertGroupMember(rhId, groupInfo, member)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, groupInfo, member)
}
} else {
break
}
@@ -30,6 +30,7 @@ import androidx.compose.ui.unit.*
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
@@ -198,6 +199,9 @@ private fun removeMemberAlert(rhId: Long?, groupInfo: GroupInfo, mem: GroupMembe
withChats {
upsertGroupMember(rhId, groupInfo, updatedMember)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, groupInfo, updatedMember)
}
}
}
},
@@ -28,6 +28,7 @@ import androidx.compose.ui.unit.*
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.*
import chat.simplex.common.views.helpers.*
@@ -65,6 +66,9 @@ fun GroupMemberInfoView(
withChats {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
withReportsChatsIfOpen {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
close.invoke()
}
}
@@ -142,6 +146,9 @@ fun GroupMemberInfoView(
withChats {
upsertGroupMember(rhId, groupInfo, mem)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, groupInfo, mem)
}
}.onFailure {
newRole.value = prevValue
}
@@ -157,6 +164,9 @@ fun GroupMemberInfoView(
withChats {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
withReportsChatsIfOpen {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
close.invoke()
}
}
@@ -171,6 +181,9 @@ fun GroupMemberInfoView(
withChats {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
withReportsChatsIfOpen {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
close.invoke()
}
}
@@ -188,6 +201,9 @@ fun GroupMemberInfoView(
withChats {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
withReportsChatsIfOpen {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
close.invoke()
}
}
@@ -203,16 +219,16 @@ fun GroupMemberInfoView(
verify = { code ->
chatModel.controller.apiVerifyGroupMember(rhId, mem.groupId, mem.groupMemberId, code)?.let { r ->
val (verified, existingCode) = r
withChats {
upsertGroupMember(
rhId,
groupInfo,
mem.copy(
activeConn = mem.activeConn?.copy(
connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null
)
)
val copy = mem.copy(
activeConn = mem.activeConn?.copy(
connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null
)
)
withChats {
upsertGroupMember(rhId, groupInfo, copy)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, groupInfo, copy)
}
r
}
@@ -246,6 +262,9 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c
withChats {
upsertGroupMember(rhId, groupInfo, removedMember)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, groupInfo, removedMember)
}
}
close?.invoke()
}
@@ -753,6 +772,9 @@ fun updateMemberSettings(rhId: Long?, gInfo: GroupInfo, member: GroupMember, mem
withChats {
upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings))
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings))
}
}
}
}
@@ -786,6 +808,9 @@ fun blockMemberForAll(rhId: Long?, gInfo: GroupInfo, member: GroupMember, blocke
withChats {
upsertGroupMember(rhId, gInfo, updatedMember)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, gInfo, updatedMember)
}
}
}
@@ -45,6 +45,9 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () ->
updateGroup(rhId, g)
currentPreferences = preferences
}
withChats {
updateGroup(rhId, g)
}
}
afterSave()
}
@@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.*
@@ -42,6 +43,9 @@ fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl
withChats {
updateGroup(rhId, gInfo)
}
withReportsChatsIfOpen {
updateGroup(rhId, gInfo)
}
close.invoke()
}
}
@@ -17,6 +17,8 @@ import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
val LocalContentTag: ProvidableCompositionLocal<MsgContentTag?> = staticCompositionLocalOf { null }
data class GroupReports(
val reportsCount: Int,
val reportsView: Boolean,
@@ -27,6 +27,7 @@ import chat.simplex.common.views.chat.item.MarkdownText
import chat.simplex.common.views.helpers.*
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.model.GroupInfo
import chat.simplex.common.platform.ColumnWithScrollBar
import chat.simplex.common.platform.chatJsonLength
@@ -53,6 +54,9 @@ fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: ()
withChats {
updateGroup(rhId, res)
}
withReportsChatsIfOpen {
updateGroup(rhId, res)
}
welcomeText.value = welcome ?: ""
}
afterSave()
@@ -13,6 +13,7 @@ import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.getChatItemIndexOrNull
import chat.simplex.common.platform.onRightClick
import chat.simplex.common.views.chat.group.LocalContentTag
@Composable
fun CIChatFeatureView(
@@ -75,9 +76,9 @@ private fun mergedFeatures(chatItem: ChatItem, chatInfo: ChatInfo): List<Feature
val m = ChatModel
val fs: ArrayList<FeatureInfo> = arrayListOf()
val icons: MutableSet<PainterBox> = mutableSetOf()
var i = getChatItemIndexOrNull(chatItem)
val reversedChatItems = m.chatItemsForContent(LocalContentTag.current).value.asReversed()
var i = getChatItemIndexOrNull(chatItem, reversedChatItems)
if (i != null) {
val reversedChatItems = m.chatItems.asReversed()
while (i < reversedChatItems.size) {
val f = featureInfo(reversedChatItems[i], chatInfo) ?: break
if (!icons.contains(f.icon)) {
@@ -28,6 +28,7 @@ import chat.simplex.common.model.ChatModel.currentUser
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.*
import chat.simplex.common.views.chat.group.LocalContentTag
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import kotlinx.datetime.Clock
@@ -500,8 +501,8 @@ fun ChatItemView(
DeleteItemMenu()
}
fun mergedGroupEventText(chatItem: ChatItem): String? {
val (count, ns) = chatModel.getConnectedMemberNames(chatItem)
fun mergedGroupEventText(chatItem: ChatItem, reversedChatItems: List<ChatItem>): String? {
val (count, ns) = chatModel.getConnectedMemberNames(chatItem, reversedChatItems)
val members = when {
ns.size == 1 -> String.format(generalGetString(MR.strings.rcv_group_event_1_member_connected), ns[0])
ns.size == 2 -> String.format(generalGetString(MR.strings.rcv_group_event_2_members_connected), ns[0], ns[1])
@@ -520,9 +521,9 @@ fun ChatItemView(
}
}
fun eventItemViewText(): AnnotatedString {
fun eventItemViewText(reversedChatItems: List<ChatItem>): AnnotatedString {
val memberDisplayName = cItem.memberDisplayName
val t = mergedGroupEventText(cItem)
val t = mergedGroupEventText(cItem, reversedChatItems)
return if (!revealed.value && t != null) {
chatEventText(t, cItem.timestampText)
} else if (memberDisplayName != null) {
@@ -536,7 +537,8 @@ fun ChatItemView(
}
@Composable fun EventItemView() {
CIEventView(eventItemViewText())
val reversedChatItems = chatModel.chatItemsForContent(LocalContentTag.current).value.asReversed()
CIEventView(eventItemViewText(reversedChatItems))
}
@Composable
@@ -729,20 +731,21 @@ fun DeleteItemAction(
deleteMessage: (Long, CIDeleteMode) -> Unit,
deleteMessages: (List<Long>) -> Unit,
) {
val contentTag = LocalContentTag.current
ItemAction(
stringResource(MR.strings.delete_verb),
painterResource(MR.images.ic_delete),
onClick = {
showMenu.value = false
if (!revealed.value) {
val currIndex = chatModel.getChatItemIndexOrNull(cItem)
val reversedChatItems = chatModel.chatItemsForContent(contentTag).value.asReversed()
val currIndex = chatModel.getChatItemIndexOrNull(cItem, reversedChatItems)
val ciCategory = cItem.mergeCategory
if (currIndex != null && ciCategory != null) {
val (prevHidden, _) = chatModel.getPrevShownChatItem(currIndex, ciCategory)
val (prevHidden, _) = chatModel.getPrevShownChatItem(currIndex, ciCategory, reversedChatItems)
val range = chatViewItemsRange(currIndex, prevHidden)
if (range != null) {
val itemIds: ArrayList<Long> = arrayListOf()
val reversedChatItems = chatModel.chatItems.asReversed()
for (i in range) {
itemIds.add(reversedChatItems[i].id)
}
@@ -12,8 +12,10 @@ import androidx.compose.runtime.*
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.chatModel
import chat.simplex.common.model.ChatModel.getChatItemIndexOrNull
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.group.LocalContentTag
import chat.simplex.common.views.helpers.generalGetString
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.stringResource
@@ -42,10 +44,10 @@ fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, revealed: State<
@Composable
private fun MergedMarkedDeletedText(chatItem: ChatItem, revealed: State<Boolean>) {
var i = getChatItemIndexOrNull(chatItem)
val reversedChatItems = chatModel.chatItemsForContent(LocalContentTag.current).value.asReversed()
var i = getChatItemIndexOrNull(chatItem, reversedChatItems)
val ciCategory = chatItem.mergeCategory
val text = if (!revealed.value && ciCategory != null && i != null) {
val reversedChatItems = ChatModel.chatItems.asReversed()
var moderated = 0
var blocked = 0
var blockedByAdmin = 0
@@ -22,6 +22,7 @@ import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.markChatTagRead
import chat.simplex.common.model.ChatModel.updateChatTagRead
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.*
@@ -252,7 +253,7 @@ suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo
fun ContactMenuItems(chat: Chat, contact: Contact, chatModel: ChatModel, showMenu: MutableState<Boolean>, showMarkRead: Boolean) {
if (contact.activeConn != null) {
if (showMarkRead) {
MarkReadChatAction(chat, chatModel, showMenu)
MarkReadChatAction(chat, showMenu)
} else {
MarkUnreadChatAction(chat, chatModel, showMenu)
}
@@ -292,7 +293,7 @@ fun GroupMenuItems(
}
else -> {
if (showMarkRead) {
MarkReadChatAction(chat, chatModel, showMenu)
MarkReadChatAction(chat, showMenu)
} else {
MarkUnreadChatAction(chat, chatModel, showMenu)
}
@@ -313,7 +314,7 @@ fun GroupMenuItems(
@Composable
fun NoteFolderMenuItems(chat: Chat, showMenu: MutableState<Boolean>, showMarkRead: Boolean) {
if (showMarkRead) {
MarkReadChatAction(chat, chatModel, showMenu)
MarkReadChatAction(chat, showMenu)
} else {
MarkUnreadChatAction(chat, chatModel, showMenu)
}
@@ -321,12 +322,12 @@ fun NoteFolderMenuItems(chat: Chat, showMenu: MutableState<Boolean>, showMarkRea
}
@Composable
fun MarkReadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
fun MarkReadChatAction(chat: Chat, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(MR.strings.mark_read),
painterResource(MR.images.ic_check),
onClick = {
markChatRead(chat, chatModel)
markChatRead(chat)
ntfManager.cancelNotificationsForChat(chat.id)
showMenu.value = false
}
@@ -563,13 +564,16 @@ private fun InvalidDataView() {
}
}
fun markChatRead(c: Chat, chatModel: ChatModel) {
fun markChatRead(c: Chat) {
var chat = c
withApi {
if (chat.chatStats.unreadCount > 0) {
withChats {
markChatItemsRead(chat.remoteHostId, chat.chatInfo)
}
withReportsChatsIfOpen {
markChatItemsRead(chat.remoteHostId, chat.chatInfo)
}
chatModel.controller.apiChatRead(
chat.remoteHostId,
chat.chatInfo.chatType,
@@ -589,6 +593,9 @@ fun markChatRead(c: Chat, chatModel: ChatModel) {
replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
markChatTagRead(chat)
}
withReportsChatsIfOpen {
replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
}
}
}
}
@@ -611,6 +618,9 @@ fun markChatUnread(chat: Chat, chatModel: ChatModel) {
replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true)))
updateChatTagRead(chat, wasUnread)
}
withReportsChatsIfOpen {
replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true)))
}
}
}
}
@@ -866,6 +876,9 @@ fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: Ch
withChats {
updateChatInfo(remoteHostId, newChatInfo)
}
withReportsChatsIfOpen {
updateChatInfo(remoteHostId, newChatInfo)
}
if (chatSettings.enableNtfs != MsgFilter.All) {
ntfManager.cancelNotificationsForChat(chatInfo.id)
}
@@ -32,6 +32,7 @@ import chat.simplex.common.model.ChatController.apiDeleteChatTag
import chat.simplex.common.model.ChatController.apiSetChatTags
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.item.ItemAction
@@ -425,6 +426,9 @@ private fun setTag(rhId: Long?, tagId: Long?, chat: Chat, close: () -> Unit) {
withChats {
updateGroup(rhId, group)
}
withReportsChatsIfOpen {
updateGroup(rhId, group)
}
}
else -> {}
@@ -462,6 +466,9 @@ private fun deleteTag(rhId: Long?, tag: ChatTag, saving: MutableState<Boolean>)
withChats {
updateGroup(rhId, group)
}
withReportsChatsIfOpen {
updateGroup(rhId, group)
}
}
else -> {}
}