mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-06-04 06:01:50 +00:00
android, desktop: refactoring to use mutex when updating chats (#4541)
* moving to mutablestate + snapshotstatelist from snapshotstatelist * android, desktop: refactoring to use mutex when updating chats * wrapped into class instead of object * fix --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
committed by
GitHub
parent
6fa3695ad6
commit
19cab39ee8
@@ -18,7 +18,7 @@ import chat.simplex.app.views.call.CallActivity
|
||||
import chat.simplex.common.helpers.*
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.ChatModel.updatingChatsMutex
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.CurrentColors
|
||||
import chat.simplex.common.ui.theme.DefaultTheme
|
||||
@@ -27,7 +27,6 @@ import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
||||
import com.jakewharton.processphoenix.ProcessPhoenix
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -86,7 +85,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
Lifecycle.Event.ON_START -> {
|
||||
isAppOnForeground = true
|
||||
if (chatModel.chatRunning.value == true) {
|
||||
updatingChatsMutex.withLock {
|
||||
withChats {
|
||||
kotlin.runCatching {
|
||||
val currentUserId = chatModel.currentUser.value?.userId
|
||||
val chats = ArrayList(chatController.apiGetChats(chatModel.remoteHostId()))
|
||||
@@ -99,7 +98,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
/** Pass old chatStats because unreadCounter can be changed already while [ChatController.apiGetChats] is executing */
|
||||
if (indexOfCurrentChat >= 0) chats[indexOfCurrentChat] = chats[indexOfCurrentChat].copy(chatStats = oldStats)
|
||||
}
|
||||
chatModel.updateChats(chats)
|
||||
updateChats(chats)
|
||||
}
|
||||
}.onFailure { Log.e(TAG, it.stackTraceToString()) }
|
||||
}
|
||||
|
||||
+300
-285
@@ -54,7 +54,9 @@ object ChatModel {
|
||||
val ctrlInitInProgress = mutableStateOf(false)
|
||||
val dbMigrationInProgress = mutableStateOf(false)
|
||||
val incompleteInitializedDbRemoved = mutableStateOf(false)
|
||||
val chats = mutableStateListOf<Chat>()
|
||||
private val _chats = mutableStateOf(SnapshotStateList<Chat>())
|
||||
val chats: State<List<Chat>> = _chats
|
||||
private val chatsContext = ChatsContext()
|
||||
// map of connections network statuses, key is agent connection id
|
||||
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
|
||||
val switchingUsersAndHosts = mutableStateOf(false)
|
||||
@@ -126,7 +128,7 @@ object ChatModel {
|
||||
val updatingProgress = mutableStateOf(null as Float?)
|
||||
var updatingRequest: Closeable? = null
|
||||
|
||||
val updatingChatsMutex: Mutex = Mutex()
|
||||
private val updatingChatsMutex: Mutex = Mutex()
|
||||
val changingActiveUserMutex: Mutex = Mutex()
|
||||
|
||||
val desktopNoUserNoRemote: Boolean @Composable get() = appPlatform.isDesktop && currentUser.value == null && currentRemoteHost.value == null
|
||||
@@ -170,11 +172,11 @@ object ChatModel {
|
||||
}
|
||||
|
||||
// toList() here is to prevent ConcurrentModificationException that is rarely happens but happens
|
||||
fun hasChat(rhId: Long?, id: String): Boolean = chats.toList().firstOrNull { it.id == id && it.remoteHostId == rhId } != null
|
||||
fun hasChat(rhId: Long?, id: String): Boolean = chats.value.firstOrNull { it.id == id && it.remoteHostId == rhId } != null
|
||||
// TODO pass rhId?
|
||||
fun getChat(id: String): Chat? = chats.toList().firstOrNull { it.id == id }
|
||||
fun getContactChat(contactId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId }
|
||||
fun getGroupChat(groupId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Group && it.chatInfo.apiId == groupId }
|
||||
fun getChat(id: String): Chat? = chats.value.firstOrNull { it.id == id }
|
||||
fun getContactChat(contactId: Long): Chat? = chats.value.firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId }
|
||||
fun getGroupChat(groupId: Long): Chat? = chats.value.firstOrNull { it.chatInfo is ChatInfo.Group && it.chatInfo.apiId == groupId }
|
||||
|
||||
fun populateGroupMembersIndexes() {
|
||||
groupMembersIndexes.clear()
|
||||
@@ -192,97 +194,102 @@ object ChatModel {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getChatIndex(rhId: Long?, id: String): Int = chats.toList().indexOfFirst { it.id == id && it.remoteHostId == rhId }
|
||||
fun addChat(chat: Chat) = chats.add(index = 0, chat)
|
||||
suspend fun <T> withChats(action: suspend ChatsContext.() -> T): T = updatingChatsMutex.withLock {
|
||||
chatsContext.action()
|
||||
}
|
||||
|
||||
fun updateChatInfo(rhId: Long?, cInfo: ChatInfo) {
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
if (i >= 0) {
|
||||
val currentCInfo = chats[i].chatInfo
|
||||
var newCInfo = cInfo
|
||||
if (currentCInfo is ChatInfo.Direct && newCInfo is ChatInfo.Direct) {
|
||||
val currentStats = currentCInfo.contact.activeConn?.connectionStats
|
||||
val newConn = newCInfo.contact.activeConn
|
||||
val newStats = newConn?.connectionStats
|
||||
if (currentStats != null && newConn != null && newStats == null) {
|
||||
newCInfo = newCInfo.copy(
|
||||
contact = newCInfo.contact.copy(
|
||||
activeConn = newConn.copy(
|
||||
connectionStats = currentStats
|
||||
class ChatsContext {
|
||||
val chats = _chats
|
||||
|
||||
fun addChat(chat: Chat) = chats.add(index = 0, chat)
|
||||
|
||||
fun updateChatInfo(rhId: Long?, cInfo: ChatInfo) {
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
if (i >= 0) {
|
||||
val currentCInfo = chats[i].chatInfo
|
||||
var newCInfo = cInfo
|
||||
if (currentCInfo is ChatInfo.Direct && newCInfo is ChatInfo.Direct) {
|
||||
val currentStats = currentCInfo.contact.activeConn?.connectionStats
|
||||
val newConn = newCInfo.contact.activeConn
|
||||
val newStats = newConn?.connectionStats
|
||||
if (currentStats != null && newConn != null && newStats == null) {
|
||||
newCInfo = newCInfo.copy(
|
||||
contact = newCInfo.contact.copy(
|
||||
activeConn = newConn.copy(
|
||||
connectionStats = currentStats
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
chats[i] = chats[i].copy(chatInfo = newCInfo)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateContactConnection(rhId: Long?, contactConnection: PendingContactConnection) = updateChat(rhId, ChatInfo.ContactConnection(contactConnection))
|
||||
|
||||
fun updateContact(rhId: Long?, contact: Contact) = updateChat(rhId, ChatInfo.Direct(contact), addMissing = contact.directOrUsed)
|
||||
|
||||
fun updateContactConnectionStats(rhId: Long?, contact: Contact, connectionStats: ConnectionStats) {
|
||||
val updatedConn = contact.activeConn?.copy(connectionStats = connectionStats)
|
||||
val updatedContact = contact.copy(activeConn = updatedConn)
|
||||
updateContact(rhId, updatedContact)
|
||||
}
|
||||
|
||||
fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo))
|
||||
|
||||
private fun updateChat(rhId: Long?, cInfo: ChatInfo, addMissing: Boolean = true) {
|
||||
if (hasChat(rhId, cInfo.id)) {
|
||||
updateChatInfo(rhId, cInfo)
|
||||
} else if (addMissing) {
|
||||
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf()))
|
||||
}
|
||||
}
|
||||
|
||||
fun updateChats(newChats: List<Chat>) {
|
||||
chats.clear()
|
||||
chats.addAll(newChats)
|
||||
|
||||
val cId = chatId.value
|
||||
// If chat is null, it was deleted in background after apiGetChats call
|
||||
if (cId != null && getChat(cId) == null) {
|
||||
chatId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
fun replaceChat(rhId: Long?, id: String, chat: Chat) {
|
||||
val i = getChatIndex(rhId, id)
|
||||
if (i >= 0) {
|
||||
chats[i] = chat
|
||||
} else {
|
||||
// invalid state, correcting
|
||||
chats.add(index = 0, chat)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) = updatingChatsMutex.withLock {
|
||||
// update previews
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
val chat: Chat
|
||||
if (i >= 0) {
|
||||
chat = chats[i]
|
||||
val newPreviewItem = when (cInfo) {
|
||||
is ChatInfo.Group -> {
|
||||
val currentPreviewItem = chat.chatItems.firstOrNull()
|
||||
if (currentPreviewItem != null) {
|
||||
if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) {
|
||||
cItem
|
||||
} else {
|
||||
currentPreviewItem
|
||||
}
|
||||
} else {
|
||||
cItem
|
||||
}
|
||||
}
|
||||
else -> cItem
|
||||
chats[i] = chats[i].copy(chatInfo = newCInfo)
|
||||
}
|
||||
chats[i] = chat.copy(
|
||||
chatItems = arrayListOf(newPreviewItem),
|
||||
chatStats =
|
||||
}
|
||||
|
||||
fun updateContactConnection(rhId: Long?, contactConnection: PendingContactConnection) = updateChat(rhId, ChatInfo.ContactConnection(contactConnection))
|
||||
|
||||
fun updateContact(rhId: Long?, contact: Contact) = updateChat(rhId, ChatInfo.Direct(contact), addMissing = contact.directOrUsed)
|
||||
|
||||
fun updateContactConnectionStats(rhId: Long?, contact: Contact, connectionStats: ConnectionStats) {
|
||||
val updatedConn = contact.activeConn?.copy(connectionStats = connectionStats)
|
||||
val updatedContact = contact.copy(activeConn = updatedConn)
|
||||
updateContact(rhId, updatedContact)
|
||||
}
|
||||
|
||||
fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo))
|
||||
|
||||
private fun updateChat(rhId: Long?, cInfo: ChatInfo, addMissing: Boolean = true) {
|
||||
if (hasChat(rhId, cInfo.id)) {
|
||||
updateChatInfo(rhId, cInfo)
|
||||
} else if (addMissing) {
|
||||
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf()))
|
||||
}
|
||||
}
|
||||
|
||||
fun updateChats(newChats: List<Chat>) {
|
||||
chats.clear()
|
||||
chats.addAll(newChats)
|
||||
|
||||
val cId = chatId.value
|
||||
// If chat is null, it was deleted in background after apiGetChats call
|
||||
if (cId != null && getChat(cId) == null) {
|
||||
chatId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
fun replaceChat(rhId: Long?, id: String, chat: Chat) {
|
||||
val i = getChatIndex(rhId, id)
|
||||
if (i >= 0) {
|
||||
chats[i] = chat
|
||||
} else {
|
||||
// invalid state, correcting
|
||||
chats.add(index = 0, chat)
|
||||
}
|
||||
}
|
||||
suspend fun addChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
|
||||
// update previews
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
val chat: Chat
|
||||
if (i >= 0) {
|
||||
chat = chats[i]
|
||||
val newPreviewItem = when (cInfo) {
|
||||
is ChatInfo.Group -> {
|
||||
val currentPreviewItem = chat.chatItems.firstOrNull()
|
||||
if (currentPreviewItem != null) {
|
||||
if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) {
|
||||
cItem
|
||||
} else {
|
||||
currentPreviewItem
|
||||
}
|
||||
} else {
|
||||
cItem
|
||||
}
|
||||
}
|
||||
else -> cItem
|
||||
}
|
||||
chats[i] = chat.copy(
|
||||
chatItems = arrayListOf(newPreviewItem),
|
||||
chatStats =
|
||||
if (cItem.meta.itemStatus is CIStatus.RcvNew) {
|
||||
val minUnreadId = if(chat.chatStats.minUnreadItemId == 0L) cItem.id else chat.chatStats.minUnreadItemId
|
||||
increaseUnreadCounter(rhId, currentUser.value!!)
|
||||
@@ -290,123 +297,197 @@ object ChatModel {
|
||||
}
|
||||
else
|
||||
chat.chatStats
|
||||
)
|
||||
if (i > 0) {
|
||||
popChat_(i)
|
||||
)
|
||||
if (i > 0) {
|
||||
chats.add(index = 0, chats.removeAt(i))
|
||||
}
|
||||
} else {
|
||||
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
|
||||
}
|
||||
} else {
|
||||
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
// add to current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
// Prevent situation when chat item already in the list received from backend
|
||||
if (chatItems.value.none { it.id == cItem.id }) {
|
||||
if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
||||
chatItems.add(kotlin.math.max(0, chatItems.value.lastIndex), cItem)
|
||||
} else {
|
||||
chatItems.add(cItem)
|
||||
withContext(Dispatchers.Main) {
|
||||
// add to current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
// Prevent situation when chat item already in the list received from backend
|
||||
if (chatItems.value.none { it.id == cItem.id }) {
|
||||
if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
||||
chatItems.add(kotlin.math.max(0, chatItems.value.lastIndex), cItem)
|
||||
} else {
|
||||
chatItems.add(cItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun upsertChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem): Boolean = updatingChatsMutex.withLock {
|
||||
// update previews
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
val chat: Chat
|
||||
val res: Boolean
|
||||
if (i >= 0) {
|
||||
chat = chats[i]
|
||||
val pItem = chat.chatItems.lastOrNull()
|
||||
if (pItem?.id == cItem.id) {
|
||||
chats[i] = chat.copy(chatItems = arrayListOf(cItem))
|
||||
if (pItem.isRcvNew && !cItem.isRcvNew) {
|
||||
// status changed from New to Read, update counter
|
||||
decreaseCounterInChat(rhId, cInfo.id)
|
||||
suspend fun upsertChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem): Boolean {
|
||||
// update previews
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
val chat: Chat
|
||||
val res: Boolean
|
||||
if (i >= 0) {
|
||||
chat = chats[i]
|
||||
val pItem = chat.chatItems.lastOrNull()
|
||||
if (pItem?.id == cItem.id) {
|
||||
chats[i] = chat.copy(chatItems = arrayListOf(cItem))
|
||||
if (pItem.isRcvNew && !cItem.isRcvNew) {
|
||||
// status changed from New to Read, update counter
|
||||
decreaseCounterInChat(rhId, cInfo.id)
|
||||
}
|
||||
}
|
||||
res = false
|
||||
} else {
|
||||
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
|
||||
res = true
|
||||
}
|
||||
return withContext(Dispatchers.Main) {
|
||||
// update current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
val items = chatItems.value
|
||||
val itemIndex = items.indexOfFirst { it.id == cItem.id }
|
||||
if (itemIndex >= 0) {
|
||||
items[itemIndex] = cItem
|
||||
false
|
||||
} else {
|
||||
val status = chatItemStatuses.remove(cItem.id)
|
||||
val ci = if (status != null && cItem.meta.itemStatus is CIStatus.SndNew) {
|
||||
cItem.copy(meta = cItem.meta.copy(itemStatus = status))
|
||||
} else {
|
||||
cItem
|
||||
}
|
||||
chatItems.add(ci)
|
||||
true
|
||||
}
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
res = false
|
||||
} else {
|
||||
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
|
||||
res = true
|
||||
}
|
||||
return withContext(Dispatchers.Main) {
|
||||
// update current chat
|
||||
|
||||
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (chatId.value == cInfo.id) {
|
||||
val items = chatItems.value
|
||||
val itemIndex = items.indexOfFirst { it.id == cItem.id }
|
||||
if (itemIndex >= 0) {
|
||||
items[itemIndex] = cItem
|
||||
}
|
||||
} else if (status != null) {
|
||||
chatItemStatuses[cItem.id] = status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
|
||||
if (cItem.isRcvNew) {
|
||||
decreaseCounterInChat(rhId, cInfo.id)
|
||||
}
|
||||
// update previews
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
val chat: Chat
|
||||
if (i >= 0) {
|
||||
chat = chats[i]
|
||||
val pItem = chat.chatItems.lastOrNull()
|
||||
if (pItem?.id == cItem.id) {
|
||||
chats[i] = chat.copy(chatItems = arrayListOf(ChatItem.deletedItemDummy))
|
||||
}
|
||||
}
|
||||
// remove from current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
val items = chatItems.value
|
||||
val itemIndex = items.indexOfFirst { it.id == cItem.id }
|
||||
if (itemIndex >= 0) {
|
||||
items[itemIndex] = cItem
|
||||
chatItems.removeAll {
|
||||
val remove = it.id == cItem.id
|
||||
if (remove) { AudioPlayer.stop(it) }
|
||||
remove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearChat(rhId: Long?, cInfo: ChatInfo) {
|
||||
// clear preview
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
if (i >= 0) {
|
||||
decreaseUnreadCounter(rhId, currentUser.value!!, chats[i].chatStats.unreadCount)
|
||||
chats[i] = chats[i].copy(chatItems = arrayListOf(), chatStats = Chat.ChatStats(), chatInfo = cInfo)
|
||||
}
|
||||
// clear current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
chatItemStatuses.clear()
|
||||
chatItems.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun markChatItemsRead(chat: Chat, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) {
|
||||
val cInfo = chat.chatInfo
|
||||
val markedRead = markItemsReadInCurrentChat(chat, range)
|
||||
// update preview
|
||||
val chatIdx = getChatIndex(chat.remoteHostId, cInfo.id)
|
||||
if (chatIdx >= 0) {
|
||||
val chat = chats[chatIdx]
|
||||
val lastId = chat.chatItems.lastOrNull()?.id
|
||||
if (lastId != null) {
|
||||
val unreadCount = unreadCountAfter ?: if (range != null) chat.chatStats.unreadCount - markedRead else 0
|
||||
decreaseUnreadCounter(chat.remoteHostId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount)
|
||||
chats[chatIdx] = chat.copy(
|
||||
chatStats = chat.chatStats.copy(
|
||||
unreadCount = unreadCount,
|
||||
// Can't use minUnreadItemId currently since chat items can have unread items between read items
|
||||
//minUnreadItemId = if (range != null) kotlin.math.max(chat.chatStats.minUnreadItemId, range.to + 1) else lastId + 1
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun decreaseCounterInChat(rhId: Long?, chatId: ChatId) {
|
||||
val chatIndex = getChatIndex(rhId, chatId)
|
||||
if (chatIndex == -1) return
|
||||
|
||||
val chat = chats[chatIndex]
|
||||
val unreadCount = kotlin.math.max(chat.chatStats.unreadCount - 1, 0)
|
||||
decreaseUnreadCounter(rhId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount)
|
||||
chats[chatIndex] = chat.copy(
|
||||
chatStats = chat.chatStats.copy(
|
||||
unreadCount = unreadCount,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun removeChat(rhId: Long?, id: String) {
|
||||
chats.removeAll { it.id == id && it.remoteHostId == rhId }
|
||||
}
|
||||
|
||||
fun upsertGroupMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember): Boolean {
|
||||
// user member was updated
|
||||
if (groupInfo.membership.groupMemberId == member.groupMemberId) {
|
||||
updateGroup(rhId, groupInfo)
|
||||
return false
|
||||
}
|
||||
// update current chat
|
||||
return if (chatId.value == groupInfo.id) {
|
||||
val memberIndex = groupMembersIndexes[member.groupMemberId]
|
||||
if (memberIndex != null) {
|
||||
groupMembers[memberIndex] = member
|
||||
false
|
||||
} else {
|
||||
val status = chatItemStatuses.remove(cItem.id)
|
||||
val ci = if (status != null && cItem.meta.itemStatus is CIStatus.SndNew) {
|
||||
cItem.copy(meta = cItem.meta.copy(itemStatus = status))
|
||||
} else {
|
||||
cItem
|
||||
}
|
||||
chatItems.add(ci)
|
||||
groupMembers.add(member)
|
||||
groupMembersIndexes[member.groupMemberId] = groupMembers.size - 1
|
||||
true
|
||||
}
|
||||
} else {
|
||||
res
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun updateGroupMemberConnectionStats(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) {
|
||||
val memberConn = member.activeConn
|
||||
if (memberConn != null) {
|
||||
val updatedConn = memberConn.copy(connectionStats = connectionStats)
|
||||
val updatedMember = member.copy(activeConn = updatedConn)
|
||||
upsertGroupMember(rhId, groupInfo, updatedMember)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (chatId.value == cInfo.id) {
|
||||
val items = chatItems.value
|
||||
val itemIndex = items.indexOfFirst { it.id == cItem.id }
|
||||
if (itemIndex >= 0) {
|
||||
items[itemIndex] = cItem
|
||||
}
|
||||
} else if (status != null) {
|
||||
chatItemStatuses[cItem.id] = status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
|
||||
if (cItem.isRcvNew) {
|
||||
decreaseCounterInChat(rhId, cInfo.id)
|
||||
}
|
||||
// update previews
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
val chat: Chat
|
||||
if (i >= 0) {
|
||||
chat = chats[i]
|
||||
val pItem = chat.chatItems.lastOrNull()
|
||||
if (pItem?.id == cItem.id) {
|
||||
chats[i] = chat.copy(chatItems = arrayListOf(ChatItem.deletedItemDummy))
|
||||
}
|
||||
}
|
||||
// remove from current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
chatItems.removeAll {
|
||||
val remove = it.id == cItem.id
|
||||
if (remove) { AudioPlayer.stop(it) }
|
||||
remove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearChat(rhId: Long?, cInfo: ChatInfo) {
|
||||
// clear preview
|
||||
val i = getChatIndex(rhId, cInfo.id)
|
||||
if (i >= 0) {
|
||||
decreaseUnreadCounter(rhId, currentUser.value!!, chats[i].chatStats.unreadCount)
|
||||
chats[i] = chats[i].copy(chatItems = arrayListOf(), chatStats = Chat.ChatStats(), chatInfo = cInfo)
|
||||
}
|
||||
// clear current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
chatItemStatuses.clear()
|
||||
chatItems.clear()
|
||||
}
|
||||
}
|
||||
private fun getChatIndex(rhId: Long?, id: String): Int = chats.value.indexOfFirst { it.id == id && it.remoteHostId == rhId }
|
||||
|
||||
fun updateCurrentUser(rhId: Long?, newProfile: Profile, preferences: FullChatPreferences? = null) {
|
||||
val current = currentUser.value ?: return
|
||||
@@ -447,28 +528,6 @@ object ChatModel {
|
||||
}
|
||||
}
|
||||
|
||||
fun markChatItemsRead(chat: Chat, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) {
|
||||
val cInfo = chat.chatInfo
|
||||
val markedRead = markItemsReadInCurrentChat(chat, range)
|
||||
// update preview
|
||||
val chatIdx = getChatIndex(chat.remoteHostId, cInfo.id)
|
||||
if (chatIdx >= 0) {
|
||||
val chat = chats[chatIdx]
|
||||
val lastId = chat.chatItems.lastOrNull()?.id
|
||||
if (lastId != null) {
|
||||
val unreadCount = unreadCountAfter ?: if (range != null) chat.chatStats.unreadCount - markedRead else 0
|
||||
decreaseUnreadCounter(chat.remoteHostId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount)
|
||||
chats[chatIdx] = chat.copy(
|
||||
chatStats = chat.chatStats.copy(
|
||||
unreadCount = unreadCount,
|
||||
// Can't use minUnreadItemId currently since chat items can have unread items between read items
|
||||
//minUnreadItemId = if (range != null) kotlin.math.max(chat.chatStats.minUnreadItemId, range.to + 1) else lastId + 1
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun markItemsReadInCurrentChat(chat: Chat, range: CC.ItemRange? = null): Int {
|
||||
val cInfo = chat.chatInfo
|
||||
var markedRead = 0
|
||||
@@ -493,20 +552,6 @@ object ChatModel {
|
||||
return markedRead
|
||||
}
|
||||
|
||||
private fun decreaseCounterInChat(rhId: Long?, chatId: ChatId) {
|
||||
val chatIndex = getChatIndex(rhId, chatId)
|
||||
if (chatIndex == -1) return
|
||||
|
||||
val chat = chats[chatIndex]
|
||||
val unreadCount = kotlin.math.max(chat.chatStats.unreadCount - 1, 0)
|
||||
decreaseUnreadCounter(rhId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount)
|
||||
chats[chatIndex] = chat.copy(
|
||||
chatStats = chat.chatStats.copy(
|
||||
unreadCount = unreadCount,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun increaseUnreadCounter(rhId: Long?, user: UserLike) {
|
||||
changeUnreadCounter(rhId, user, 1)
|
||||
}
|
||||
@@ -600,11 +645,6 @@ object ChatModel {
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun popChat_(i: Int) {
|
||||
val chat = chats.removeAt(i)
|
||||
chats.add(index = 0, chat)
|
||||
}
|
||||
|
||||
fun replaceConnReqView(id: String, withId: String) {
|
||||
if (id == showingInvitation.value?.connId) {
|
||||
showingInvitation.value = null
|
||||
@@ -629,41 +669,6 @@ object ChatModel {
|
||||
showingInvitation.value = showingInvitation.value?.copy(connChatUsed = true)
|
||||
}
|
||||
|
||||
fun removeChat(rhId: Long?, id: String) {
|
||||
chats.removeAll { it.id == id && it.remoteHostId == rhId }
|
||||
}
|
||||
|
||||
fun upsertGroupMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember): Boolean {
|
||||
// user member was updated
|
||||
if (groupInfo.membership.groupMemberId == member.groupMemberId) {
|
||||
updateGroup(rhId, groupInfo)
|
||||
return false
|
||||
}
|
||||
// update current chat
|
||||
return if (chatId.value == groupInfo.id) {
|
||||
val memberIndex = groupMembersIndexes[member.groupMemberId]
|
||||
if (memberIndex != null) {
|
||||
groupMembers[memberIndex] = member
|
||||
false
|
||||
} else {
|
||||
groupMembers.add(member)
|
||||
groupMembersIndexes[member.groupMemberId] = groupMembers.size - 1
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun updateGroupMemberConnectionStats(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) {
|
||||
val memberConn = member.activeConn
|
||||
if (memberConn != null) {
|
||||
val updatedConn = memberConn.copy(connectionStats = connectionStats)
|
||||
val updatedMember = member.copy(activeConn = updatedConn)
|
||||
upsertGroupMember(rhId, groupInfo, updatedMember)
|
||||
}
|
||||
}
|
||||
|
||||
fun setContactNetworkStatus(contact: Contact, status: NetworkStatus) {
|
||||
val conn = contact.activeConn
|
||||
if (conn != null) {
|
||||
@@ -2108,45 +2113,55 @@ data class ChatItem (
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.add(index: Int, chatItem: ChatItem) {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(value); add(index, chatItem) }
|
||||
fun <T> MutableState<SnapshotStateList<T>>.add(index: Int, elem: T) {
|
||||
value = SnapshotStateList<T>().apply { addAll(value); add(index, elem) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.add(chatItem: ChatItem) {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(value); add(chatItem) }
|
||||
fun <T> MutableState<SnapshotStateList<T>>.add(elem: T) {
|
||||
value = SnapshotStateList<T>().apply { addAll(value); add(elem) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.addAll(index: Int, chatItems: List<ChatItem>) {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(value); addAll(index, chatItems) }
|
||||
fun <T> MutableState<SnapshotStateList<T>>.addAll(index: Int, elems: List<T>) {
|
||||
value = SnapshotStateList<T>().apply { addAll(value); addAll(index, elems) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.addAll(chatItems: List<ChatItem>) {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(value); addAll(chatItems) }
|
||||
fun <T> MutableState<SnapshotStateList<T>>.addAll(elems: List<T>) {
|
||||
value = SnapshotStateList<T>().apply { addAll(value); addAll(elems) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.removeAll(block: (ChatItem) -> Boolean) {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(value); removeAll(block) }
|
||||
fun <T> MutableState<SnapshotStateList<T>>.removeAll(block: (T) -> Boolean) {
|
||||
value = SnapshotStateList<T>().apply { addAll(value); removeAll(block) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.removeAt(index: Int) {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(value); removeAt(index) }
|
||||
fun <T> MutableState<SnapshotStateList<T>>.removeAt(index: Int): T {
|
||||
val new = SnapshotStateList<T>()
|
||||
new.addAll(value)
|
||||
val res = new.removeAt(index)
|
||||
value = new
|
||||
return res
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.removeLast() {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(value); removeLast() }
|
||||
fun <T> MutableState<SnapshotStateList<T>>.removeLast() {
|
||||
value = SnapshotStateList<T>().apply { addAll(value); removeLast() }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.replaceAll(chatItems: List<ChatItem>) {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(chatItems) }
|
||||
fun <T> MutableState<SnapshotStateList<T>>.replaceAll(elems: List<T>) {
|
||||
value = SnapshotStateList<T>().apply { addAll(elems) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.clear() {
|
||||
value = SnapshotStateList<ChatItem>()
|
||||
fun <T> MutableState<SnapshotStateList<T>>.clear() {
|
||||
value = SnapshotStateList<T>()
|
||||
}
|
||||
|
||||
fun State<SnapshotStateList<ChatItem>>.asReversed(): MutableList<ChatItem> = value.asReversed()
|
||||
fun <T> State<SnapshotStateList<T>>.asReversed(): MutableList<T> = value.asReversed()
|
||||
|
||||
val State<List<ChatItem>>.size: Int get() = value.size
|
||||
fun <T> State<SnapshotStateList<T>>.toList(): List<T> = value.toList()
|
||||
|
||||
operator fun <T> State<SnapshotStateList<T>>.get(i: Int): T = value[i]
|
||||
|
||||
operator fun <T> State<SnapshotStateList<T>>.set(index: Int, elem: T) { value[index] = elem }
|
||||
|
||||
val State<List<Any>>.size: Int get() = value.size
|
||||
|
||||
enum class CIMergeCategory {
|
||||
MemberConnected,
|
||||
|
||||
+157
-74
@@ -15,8 +15,8 @@ import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import chat.simplex.common.model.ChatController.getNetCfg
|
||||
import chat.simplex.common.model.ChatController.setNetCfg
|
||||
import chat.simplex.common.model.ChatModel.updatingChatsMutex
|
||||
import chat.simplex.common.model.ChatModel.changingActiveUserMutex
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
@@ -478,9 +478,9 @@ object ChatController {
|
||||
}
|
||||
Log.d(TAG, "startChat: started")
|
||||
} else {
|
||||
updatingChatsMutex.withLock {
|
||||
withChats {
|
||||
val chats = apiGetChats(null)
|
||||
chatModel.updateChats(chats)
|
||||
updateChats(chats)
|
||||
}
|
||||
Log.d(TAG, "startChat: running")
|
||||
}
|
||||
@@ -558,9 +558,9 @@ object ChatController {
|
||||
val hasUser = chatModel.currentUser.value != null
|
||||
chatModel.userAddress.value = if (hasUser) apiGetUserAddress(rhId) else null
|
||||
chatModel.chatItemTTL.value = if (hasUser) getChatItemTTL(rhId) else ChatItemTTL.None
|
||||
updatingChatsMutex.withLock {
|
||||
withChats {
|
||||
val chats = apiGetChats(rhId)
|
||||
chatModel.updateChats(chats)
|
||||
updateChats(chats)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1180,7 +1180,9 @@ object ChatController {
|
||||
suspend fun deleteChat(chat: Chat, notify: Boolean? = null) {
|
||||
val cInfo = chat.chatInfo
|
||||
if (apiDeleteChat(rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, notify = notify)) {
|
||||
chatModel.removeChat(chat.remoteHostId, cInfo.id)
|
||||
withChats {
|
||||
removeChat(chat.remoteHostId, cInfo.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1211,7 +1213,9 @@ object ChatController {
|
||||
withBGApi {
|
||||
val updatedChatInfo = apiClearChat(chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId)
|
||||
if (updatedChatInfo != null) {
|
||||
chatModel.clearChat(chat.remoteHostId, updatedChatInfo)
|
||||
withChats {
|
||||
clearChat(chat.remoteHostId, updatedChatInfo)
|
||||
}
|
||||
ntfManager.cancelNotificationsForChat(chat.chatInfo.id)
|
||||
close?.invoke()
|
||||
}
|
||||
@@ -1546,10 +1550,12 @@ object ChatController {
|
||||
val r = sendCmd(rh, CC.ApiJoinGroup(groupId))
|
||||
when (r) {
|
||||
is CR.UserAcceptedGroupSent ->
|
||||
chatModel.updateGroup(rh, r.groupInfo)
|
||||
withChats {
|
||||
updateGroup(rh, r.groupInfo)
|
||||
}
|
||||
is CR.ChatCmdError -> {
|
||||
val e = r.chatError
|
||||
suspend fun deleteGroup() { if (apiDeleteChat(rh, ChatType.Group, groupId)) { chatModel.removeChat(rh, "#$groupId") } }
|
||||
suspend fun deleteGroup() { if (apiDeleteChat(rh, ChatType.Group, groupId)) { withChats { removeChat(rh, "#$groupId") } } }
|
||||
if (e is ChatError.ChatErrorAgent && e.agentError is AgentErrorType.SMP && e.agentError.smpErr is SMPErrorType.AUTH) {
|
||||
deleteGroup()
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.alert_title_group_invitation_expired), generalGetString(MR.strings.alert_message_group_invitation_expired))
|
||||
@@ -1703,7 +1709,9 @@ object ChatController {
|
||||
val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature, param = param)
|
||||
val toContact = apiSetContactPrefs(rh, contact.contactId, prefs)
|
||||
if (toContact != null) {
|
||||
chatModel.updateContact(rh, toContact)
|
||||
withChats {
|
||||
updateContact(rh, toContact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1973,16 +1981,20 @@ object ChatController {
|
||||
when (r) {
|
||||
is CR.ContactDeletedByContact -> {
|
||||
if (active(r.user) && r.contact.directOrUsed) {
|
||||
chatModel.updateContact(rhId, r.contact)
|
||||
withChats {
|
||||
updateContact(rhId, r.contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.ContactConnected -> {
|
||||
if (active(r.user) && r.contact.directOrUsed) {
|
||||
chatModel.updateContact(rhId, r.contact)
|
||||
val conn = r.contact.activeConn
|
||||
if (conn != null) {
|
||||
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
|
||||
chatModel.removeChat(rhId, conn.id)
|
||||
withChats {
|
||||
updateContact(rhId, r.contact)
|
||||
val conn = r.contact.activeConn
|
||||
if (conn != null) {
|
||||
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
|
||||
removeChat(rhId, conn.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (r.contact.directOrUsed) {
|
||||
@@ -1992,21 +2004,25 @@ object ChatController {
|
||||
}
|
||||
is CR.ContactConnecting -> {
|
||||
if (active(r.user) && r.contact.directOrUsed) {
|
||||
chatModel.updateContact(rhId, r.contact)
|
||||
val conn = r.contact.activeConn
|
||||
if (conn != null) {
|
||||
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
|
||||
chatModel.removeChat(rhId, conn.id)
|
||||
withChats {
|
||||
updateContact(rhId, r.contact)
|
||||
val conn = r.contact.activeConn
|
||||
if (conn != null) {
|
||||
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
|
||||
removeChat(rhId, conn.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.ContactSndReady -> {
|
||||
if (active(r.user) && r.contact.directOrUsed) {
|
||||
chatModel.updateContact(rhId, r.contact)
|
||||
val conn = r.contact.activeConn
|
||||
if (conn != null) {
|
||||
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
|
||||
chatModel.removeChat(rhId, conn.id)
|
||||
withChats {
|
||||
updateContact(rhId, r.contact)
|
||||
val conn = r.contact.activeConn
|
||||
if (conn != null) {
|
||||
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
|
||||
removeChat(rhId, conn.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
chatModel.setContactNetworkStatus(r.contact, NetworkStatus.Connected())
|
||||
@@ -2015,10 +2031,12 @@ object ChatController {
|
||||
val contactRequest = r.contactRequest
|
||||
val cInfo = ChatInfo.ContactRequest(contactRequest)
|
||||
if (active(r.user)) {
|
||||
if (chatModel.hasChat(rhId, contactRequest.id)) {
|
||||
chatModel.updateChatInfo(rhId, cInfo)
|
||||
} else {
|
||||
chatModel.addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf()))
|
||||
withChats {
|
||||
if (chatModel.hasChat(rhId, contactRequest.id)) {
|
||||
updateChatInfo(rhId, cInfo)
|
||||
} else {
|
||||
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf()))
|
||||
}
|
||||
}
|
||||
}
|
||||
ntfManager.notifyContactRequestReceived(r.user, cInfo)
|
||||
@@ -2026,12 +2044,16 @@ object ChatController {
|
||||
is CR.ContactUpdated -> {
|
||||
if (active(r.user) && chatModel.hasChat(rhId, r.toContact.id)) {
|
||||
val cInfo = ChatInfo.Direct(r.toContact)
|
||||
chatModel.updateChatInfo(rhId, cInfo)
|
||||
withChats {
|
||||
updateChatInfo(rhId, cInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.GroupMemberUpdated -> {
|
||||
if (active(r.user)) {
|
||||
chatModel.upsertGroupMember(rhId, r.groupInfo, r.toMember)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, r.groupInfo, r.toMember)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.ContactsMerged -> {
|
||||
@@ -2039,7 +2061,9 @@ object ChatController {
|
||||
if (chatModel.chatId.value == r.mergedContact.id) {
|
||||
chatModel.chatId.value = r.intoContact.id
|
||||
}
|
||||
chatModel.removeChat(rhId, r.mergedContact.id)
|
||||
withChats {
|
||||
removeChat(rhId, r.mergedContact.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
// ContactsSubscribed, ContactsDisconnected and ContactSubSummary are only used in CLI,
|
||||
@@ -2049,7 +2073,9 @@ object ChatController {
|
||||
is CR.ContactSubSummary -> {
|
||||
for (sub in r.contactSubscriptions) {
|
||||
if (active(r.user)) {
|
||||
chatModel.updateContact(rhId, sub.contact)
|
||||
withChats {
|
||||
updateContact(rhId, sub.contact)
|
||||
}
|
||||
}
|
||||
val err = sub.contactError
|
||||
if (err == null) {
|
||||
@@ -2073,7 +2099,9 @@ object ChatController {
|
||||
val cInfo = r.chatItem.chatInfo
|
||||
val cItem = r.chatItem.chatItem
|
||||
if (active(r.user)) {
|
||||
chatModel.addChatItem(rhId, cInfo, cItem)
|
||||
withChats {
|
||||
addChatItem(rhId, cInfo, cItem)
|
||||
}
|
||||
} else if (cItem.isRcvNew && cInfo.ntfsEnabled) {
|
||||
chatModel.increaseUnreadCounter(rhId, r.user)
|
||||
}
|
||||
@@ -2094,14 +2122,18 @@ object ChatController {
|
||||
val cInfo = r.chatItem.chatInfo
|
||||
val cItem = r.chatItem.chatItem
|
||||
if (!cItem.isDeletedContent && active(r.user)) {
|
||||
chatModel.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
|
||||
withChats {
|
||||
updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.ChatItemUpdated ->
|
||||
chatItemSimpleUpdate(rhId, r.user, r.chatItem)
|
||||
is CR.ChatItemReaction -> {
|
||||
if (active(r.user)) {
|
||||
chatModel.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
|
||||
withChats {
|
||||
updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.ChatItemsDeleted -> {
|
||||
@@ -2127,82 +2159,113 @@ object ChatController {
|
||||
generalGetString(if (toChatItem != null) MR.strings.marked_deleted_description else MR.strings.deleted_description)
|
||||
)
|
||||
}
|
||||
if (toChatItem == null) {
|
||||
chatModel.removeChatItem(rhId, cInfo, cItem)
|
||||
} else {
|
||||
chatModel.upsertChatItem(rhId, cInfo, toChatItem.chatItem)
|
||||
withChats {
|
||||
if (toChatItem == null) {
|
||||
removeChatItem(rhId, cInfo, cItem)
|
||||
} else {
|
||||
upsertChatItem(rhId, cInfo, toChatItem.chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.ReceivedGroupInvitation -> {
|
||||
if (active(r.user)) {
|
||||
chatModel.updateGroup(rhId, r.groupInfo) // update so that repeat group invitations are not duplicated
|
||||
withChats {
|
||||
// update so that repeat group invitations are not duplicated
|
||||
updateGroup(rhId, r.groupInfo)
|
||||
}
|
||||
// TODO NtfManager.shared.notifyGroupInvitation
|
||||
}
|
||||
}
|
||||
is CR.UserAcceptedGroupSent -> {
|
||||
if (!active(r.user)) return
|
||||
|
||||
chatModel.updateGroup(rhId, r.groupInfo)
|
||||
val conn = r.hostContact?.activeConn
|
||||
if (conn != null) {
|
||||
chatModel.replaceConnReqView(conn.id, "#${r.groupInfo.groupId}")
|
||||
chatModel.removeChat(rhId, conn.id)
|
||||
withChats {
|
||||
updateGroup(rhId, r.groupInfo)
|
||||
val conn = r.hostContact?.activeConn
|
||||
if (conn != null) {
|
||||
chatModel.replaceConnReqView(conn.id, "#${r.groupInfo.groupId}")
|
||||
removeChat(rhId, conn.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.GroupLinkConnecting -> {
|
||||
if (!active(r.user)) return
|
||||
|
||||
chatModel.updateGroup(rhId, r.groupInfo)
|
||||
val hostConn = r.hostMember.activeConn
|
||||
if (hostConn != null) {
|
||||
chatModel.replaceConnReqView(hostConn.id, "#${r.groupInfo.groupId}")
|
||||
chatModel.removeChat(rhId, hostConn.id)
|
||||
withChats {
|
||||
updateGroup(rhId, r.groupInfo)
|
||||
val hostConn = r.hostMember.activeConn
|
||||
if (hostConn != null) {
|
||||
chatModel.replaceConnReqView(hostConn.id, "#${r.groupInfo.groupId}")
|
||||
removeChat(rhId, hostConn.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.JoinedGroupMemberConnecting ->
|
||||
if (active(r.user)) {
|
||||
chatModel.upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
}
|
||||
}
|
||||
is CR.DeletedMemberUser -> // TODO update user member
|
||||
if (active(r.user)) {
|
||||
chatModel.updateGroup(rhId, r.groupInfo)
|
||||
withChats {
|
||||
updateGroup(rhId, r.groupInfo)
|
||||
}
|
||||
}
|
||||
is CR.DeletedMember ->
|
||||
if (active(r.user)) {
|
||||
chatModel.upsertGroupMember(rhId, r.groupInfo, r.deletedMember)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, r.groupInfo, r.deletedMember)
|
||||
}
|
||||
}
|
||||
is CR.LeftMember ->
|
||||
if (active(r.user)) {
|
||||
chatModel.upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
}
|
||||
}
|
||||
is CR.MemberRole ->
|
||||
if (active(r.user)) {
|
||||
chatModel.upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
}
|
||||
}
|
||||
is CR.MemberRoleUser ->
|
||||
if (active(r.user)) {
|
||||
chatModel.upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
}
|
||||
}
|
||||
is CR.MemberBlockedForAll ->
|
||||
if (active(r.user)) {
|
||||
chatModel.upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
}
|
||||
}
|
||||
is CR.GroupDeleted -> // TODO update user member
|
||||
if (active(r.user)) {
|
||||
chatModel.updateGroup(rhId, r.groupInfo)
|
||||
withChats {
|
||||
updateGroup(rhId, r.groupInfo)
|
||||
}
|
||||
}
|
||||
is CR.UserJoinedGroup ->
|
||||
if (active(r.user)) {
|
||||
chatModel.updateGroup(rhId, r.groupInfo)
|
||||
withChats {
|
||||
updateGroup(rhId, r.groupInfo)
|
||||
}
|
||||
}
|
||||
is CR.JoinedGroupMember ->
|
||||
if (active(r.user)) {
|
||||
chatModel.upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
}
|
||||
}
|
||||
is CR.ConnectedToGroupMember -> {
|
||||
if (active(r.user)) {
|
||||
chatModel.upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, r.groupInfo, r.member)
|
||||
}
|
||||
}
|
||||
if (r.memberContact != null) {
|
||||
chatModel.setContactNetworkStatus(r.memberContact, NetworkStatus.Connected())
|
||||
@@ -2210,11 +2273,15 @@ object ChatController {
|
||||
}
|
||||
is CR.GroupUpdated ->
|
||||
if (active(r.user)) {
|
||||
chatModel.updateGroup(rhId, r.toGroup)
|
||||
withChats {
|
||||
updateGroup(rhId, r.toGroup)
|
||||
}
|
||||
}
|
||||
is CR.NewMemberContactReceivedInv ->
|
||||
if (active(r.user)) {
|
||||
chatModel.updateContact(rhId, r.contact)
|
||||
withChats {
|
||||
updateContact(rhId, r.contact)
|
||||
}
|
||||
}
|
||||
is CR.RcvFileStart ->
|
||||
chatItemSimpleUpdate(rhId, r.user, r.chatItem)
|
||||
@@ -2314,19 +2381,27 @@ object ChatController {
|
||||
}
|
||||
is CR.ContactSwitch ->
|
||||
if (active(r.user)) {
|
||||
chatModel.updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats)
|
||||
withChats {
|
||||
updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats)
|
||||
}
|
||||
}
|
||||
is CR.GroupMemberSwitch ->
|
||||
if (active(r.user)) {
|
||||
chatModel.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.switchProgress.connectionStats)
|
||||
withChats {
|
||||
updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.switchProgress.connectionStats)
|
||||
}
|
||||
}
|
||||
is CR.ContactRatchetSync ->
|
||||
if (active(r.user)) {
|
||||
chatModel.updateContactConnectionStats(rhId, r.contact, r.ratchetSyncProgress.connectionStats)
|
||||
withChats {
|
||||
updateContactConnectionStats(rhId, r.contact, r.ratchetSyncProgress.connectionStats)
|
||||
}
|
||||
}
|
||||
is CR.GroupMemberRatchetSync ->
|
||||
if (active(r.user)) {
|
||||
chatModel.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats)
|
||||
withChats {
|
||||
updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats)
|
||||
}
|
||||
}
|
||||
is CR.RemoteHostSessionCode -> {
|
||||
chatModel.remoteHostPairing.value = r.remoteHost_ to RemoteHostSessionState.PendingConfirmation(r.sessionCode)
|
||||
@@ -2338,7 +2413,9 @@ object ChatController {
|
||||
}
|
||||
is CR.ContactDisabled -> {
|
||||
if (active(r.user)) {
|
||||
chatModel.updateContact(rhId, r.contact)
|
||||
withChats {
|
||||
updateContact(rhId, r.contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.RemoteHostStopped -> {
|
||||
@@ -2459,7 +2536,9 @@ object ChatController {
|
||||
}
|
||||
is CR.ContactPQEnabled ->
|
||||
if (active(r.user)) {
|
||||
chatModel.updateContact(rhId, r.contact)
|
||||
withChats {
|
||||
updateContact(rhId, r.contact)
|
||||
}
|
||||
}
|
||||
is CR.ChatRespError -> when {
|
||||
r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> {
|
||||
@@ -2535,7 +2614,9 @@ object ChatController {
|
||||
suspend fun leaveGroup(rh: Long?, groupId: Long) {
|
||||
val groupInfo = apiLeaveGroup(rh, groupId)
|
||||
if (groupInfo != null) {
|
||||
chatModel.updateGroup(rh, groupInfo)
|
||||
withChats {
|
||||
updateGroup(rh, groupInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2545,7 +2626,7 @@ object ChatController {
|
||||
val notify = { ntfManager.notifyMessageReceived(user, cInfo, cItem) }
|
||||
if (!activeUser(rh, user)) {
|
||||
notify()
|
||||
} else if (chatModel.upsertChatItem(rh, cInfo, cItem)) {
|
||||
} else if (withChats { upsertChatItem(rh, cInfo, cItem) }) {
|
||||
notify()
|
||||
} else if (cItem.content is CIContent.RcvCall && cItem.content.status == CICallStatus.Missed) {
|
||||
notify()
|
||||
@@ -2589,7 +2670,9 @@ object ChatController {
|
||||
chatModel.currentUser.value = user
|
||||
if (user == null) {
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chats.clear()
|
||||
withChats {
|
||||
chats.clear()
|
||||
}
|
||||
}
|
||||
val statuses = apiGetNetworkStatuses(rhId)
|
||||
if (statuses != null) {
|
||||
|
||||
+35
-18
@@ -31,6 +31,7 @@ import androidx.compose.ui.unit.sp
|
||||
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.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
@@ -57,7 +58,7 @@ fun ChatInfoView(
|
||||
) {
|
||||
BackHandler(onBack = close)
|
||||
val contact = rememberUpdatedState(contact).value
|
||||
val chat = remember(contact.id) { chatModel.chats.firstOrNull { it.id == contact.id } }
|
||||
val chat = remember(contact.id) { chatModel.chats.value.firstOrNull { it.id == contact.id } }
|
||||
val currentUser = remember { chatModel.currentUser }.value
|
||||
val connStats = remember(contact.id, connectionStats) { mutableStateOf(connectionStats) }
|
||||
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
||||
@@ -102,7 +103,9 @@ fun ChatInfoView(
|
||||
val cStats = chatModel.controller.apiSwitchContact(chatRh, contact.contactId)
|
||||
connStats.value = cStats
|
||||
if (cStats != null) {
|
||||
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
|
||||
withChats {
|
||||
updateContactConnectionStats(chatRh, contact, cStats)
|
||||
}
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
@@ -114,7 +117,9 @@ fun ChatInfoView(
|
||||
val cStats = chatModel.controller.apiAbortSwitchContact(chatRh, contact.contactId)
|
||||
connStats.value = cStats
|
||||
if (cStats != null) {
|
||||
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
|
||||
withChats {
|
||||
updateContactConnectionStats(chatRh, contact, cStats)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -124,7 +129,9 @@ fun ChatInfoView(
|
||||
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
|
||||
connStats.value = cStats
|
||||
if (cStats != null) {
|
||||
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
|
||||
withChats {
|
||||
updateContactConnectionStats(chatRh, contact, cStats)
|
||||
}
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
@@ -135,7 +142,9 @@ fun ChatInfoView(
|
||||
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = true)
|
||||
connStats.value = cStats
|
||||
if (cStats != null) {
|
||||
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
|
||||
withChats {
|
||||
updateContactConnectionStats(chatRh, contact, cStats)
|
||||
}
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
@@ -151,14 +160,16 @@ fun ChatInfoView(
|
||||
verify = { code ->
|
||||
chatModel.controller.apiVerifyContact(chatRh, ct.contactId, code)?.let { r ->
|
||||
val (verified, existingCode) = r
|
||||
chatModel.updateContact(
|
||||
chatRh,
|
||||
ct.copy(
|
||||
activeConn = ct.activeConn?.copy(
|
||||
connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null
|
||||
withChats {
|
||||
updateContact(
|
||||
chatRh,
|
||||
ct.copy(
|
||||
activeConn = ct.activeConn?.copy(
|
||||
connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
r
|
||||
}
|
||||
},
|
||||
@@ -247,7 +258,9 @@ fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, notify
|
||||
val chatRh = chat.remoteHostId
|
||||
val r = chatModel.controller.apiDeleteChat(chatRh, chatInfo.chatType, chatInfo.apiId, notify)
|
||||
if (r) {
|
||||
chatModel.removeChat(chatRh, chatInfo.id)
|
||||
withChats {
|
||||
removeChat(chatRh, chatInfo.id)
|
||||
}
|
||||
if (chatModel.chatId.value == chatInfo.id) {
|
||||
chatModel.chatId.value = null
|
||||
ModalManager.end.closeModals()
|
||||
@@ -347,7 +360,7 @@ fun ChatInfoLayout(
|
||||
|
||||
WallpaperButton {
|
||||
ModalManager.end.showModal {
|
||||
val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } }
|
||||
val chat = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chat.id } } }
|
||||
val c = chat.value
|
||||
if (c != null) {
|
||||
ChatWallpaperEditorModal(c)
|
||||
@@ -768,10 +781,12 @@ suspend fun save(applyToMode: DefaultThemeMode?, newTheme: ThemeModeOverride?, c
|
||||
wallpaperFilesToDelete.forEach(::removeWallpaperFile)
|
||||
|
||||
if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) {
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes)))
|
||||
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||
chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes)))
|
||||
withChats {
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes)))
|
||||
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||
updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -779,7 +794,9 @@ suspend fun save(applyToMode: DefaultThemeMode?, newTheme: ThemeModeOverride?, c
|
||||
private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi {
|
||||
val chatRh = chat.remoteHostId
|
||||
chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let {
|
||||
chatModel.updateContact(chatRh, it)
|
||||
withChats {
|
||||
updateContact(chatRh, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-26
@@ -25,6 +25,7 @@ import androidx.compose.ui.unit.*
|
||||
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.ui.theme.*
|
||||
import chat.simplex.common.views.call.*
|
||||
import chat.simplex.common.views.chat.group.*
|
||||
@@ -45,7 +46,7 @@ import kotlin.math.sign
|
||||
|
||||
@Composable
|
||||
fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: String) -> Unit) {
|
||||
val activeChat = remember { mutableStateOf(chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatId }) }
|
||||
val activeChat = remember { mutableStateOf(chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == chatId }) }
|
||||
val searchText = rememberSaveable { mutableStateOf("") }
|
||||
val user = chatModel.currentUser.value
|
||||
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
|
||||
@@ -87,7 +88,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
* TODO: Re-write [ChatModel.chats] logic to a new list assignment instead of changing content of mutableList to prevent that
|
||||
* */
|
||||
try {
|
||||
chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }
|
||||
chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }
|
||||
} catch (e: ConcurrentModificationException) {
|
||||
Log.e(TAG, e.stackTraceToString())
|
||||
null
|
||||
@@ -112,7 +113,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
// Having activeChat reloaded on every change in it is inefficient (UI lags)
|
||||
val unreadCount = remember {
|
||||
derivedStateOf {
|
||||
chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }?.chatStats?.unreadCount ?: 0
|
||||
chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }?.chatStats?.unreadCount ?: 0
|
||||
}
|
||||
}
|
||||
val clipboard = LocalClipboardManager.current
|
||||
@@ -268,10 +269,12 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
if (deleted != null) {
|
||||
deletedChatItem = deleted.deletedChatItem.chatItem
|
||||
toChatItem = deleted.toChatItem?.chatItem
|
||||
if (toChatItem != null) {
|
||||
chatModel.upsertChatItem(chatRh, cInfo, toChatItem)
|
||||
} else {
|
||||
chatModel.removeChatItem(chatRh, cInfo, deletedChatItem)
|
||||
withChats {
|
||||
if (toChatItem != null) {
|
||||
upsertChatItem(chatRh, cInfo, toChatItem)
|
||||
} else {
|
||||
removeChatItem(chatRh, cInfo, deletedChatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,8 +287,10 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
chatRh, chatInfo.chatType, chatInfo.apiId, itemIds, CIDeleteMode.cidmInternal
|
||||
)
|
||||
if (deleted != null) {
|
||||
for (di in deleted) {
|
||||
chatModel.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
|
||||
withChats {
|
||||
for (di in deleted) {
|
||||
removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -351,7 +356,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
if (r != null) {
|
||||
val contactStats = r.first
|
||||
if (contactStats != null)
|
||||
chatModel.updateContactConnectionStats(chatRh, contact, contactStats)
|
||||
withChats {
|
||||
updateContactConnectionStats(chatRh, contact, contactStats)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -361,7 +368,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
if (r != null) {
|
||||
val memStats = r.second
|
||||
if (memStats != null) {
|
||||
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
|
||||
withChats {
|
||||
updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,7 +379,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
withBGApi {
|
||||
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
|
||||
if (cStats != null) {
|
||||
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
|
||||
withChats {
|
||||
updateContactConnectionStats(chatRh, contact, cStats)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -378,7 +389,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
withBGApi {
|
||||
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
|
||||
if (r != null) {
|
||||
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
|
||||
withChats {
|
||||
updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -399,7 +412,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
reaction = reaction
|
||||
)
|
||||
if (updatedCI != null) {
|
||||
chatModel.updateChatItem(cInfo, updatedCI)
|
||||
withChats {
|
||||
updateChatItem(cInfo, updatedCI)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -464,15 +479,17 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
}
|
||||
},
|
||||
markRead = { range, unreadCountAfter ->
|
||||
chatModel.markChatItemsRead(chat, range, unreadCountAfter)
|
||||
ntfManager.cancelNotificationsForChat(chat.id)
|
||||
withBGApi {
|
||||
chatModel.controller.apiChatRead(
|
||||
chatRh,
|
||||
chat.chatInfo.chatType,
|
||||
chat.chatInfo.apiId,
|
||||
range
|
||||
)
|
||||
withChats {
|
||||
markChatItemsRead(chat, range, unreadCountAfter)
|
||||
ntfManager.cancelNotificationsForChat(chat.id)
|
||||
chatModel.controller.apiChatRead(
|
||||
chatRh,
|
||||
chat.chatInfo.chatType,
|
||||
chat.chatInfo.apiId,
|
||||
range
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) },
|
||||
@@ -1141,7 +1158,7 @@ private fun ScrollToBottom(chatId: ChatId, listState: LazyListState, chatItems:
|
||||
.filter { listState.layoutInfo.visibleItemsInfo.firstOrNull()?.key != it }
|
||||
.collect {
|
||||
try {
|
||||
if (listState.firstVisibleItemIndex == 0 || (listState.firstVisibleItemIndex == 1 && listState.layoutInfo.totalItemsCount == chatItems.size)) {
|
||||
if (listState.firstVisibleItemIndex == 0 || (listState.firstVisibleItemIndex == 1 && listState.layoutInfo.totalItemsCount == chatItems.value.size)) {
|
||||
if (appPlatform.isAndroid) listState.animateScrollToItem(0) else listState.scrollToItem(0)
|
||||
} else {
|
||||
if (appPlatform.isAndroid) listState.animateScrollBy(scrollDistance) else listState.scrollBy(scrollDistance)
|
||||
@@ -1245,7 +1262,7 @@ fun BoxWithConstraintsScope.FloatingButtons(
|
||||
painterResource(MR.images.ic_check),
|
||||
onClick = {
|
||||
markRead(
|
||||
CC.ItemRange(minUnreadItemId, chatItems.value[chatItems.size - listState.layoutInfo.visibleItemsInfo.lastIndex - 1].id - 1),
|
||||
CC.ItemRange(minUnreadItemId, chatItems.value[chatItems.value.size - listState.layoutInfo.visibleItemsInfo.lastIndex - 1].id - 1),
|
||||
bottomUnreadCount
|
||||
)
|
||||
showDropDown.value = false
|
||||
@@ -1388,8 +1405,10 @@ private fun markUnreadChatAsRead(activeChat: MutableState<Chat?>, chatModel: Cha
|
||||
false
|
||||
)
|
||||
if (success && chat.id == activeChat.value?.id) {
|
||||
activeChat.value = chat.copy(chatStats = chat.chatStats.copy(unreadChat = false))
|
||||
chatModel.replaceChat(chatRh, chat.id, activeChat.value!!)
|
||||
withChats {
|
||||
activeChat.value = chat.copy(chatStats = chat.chatStats.copy(unreadChat = false))
|
||||
replaceChat(chatRh, chat.id, activeChat.value!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-4
@@ -22,6 +22,7 @@ import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.model.ChatModel.filesToDelete
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chat.item.*
|
||||
@@ -393,7 +394,9 @@ fun ComposeView(
|
||||
ttl = ttl
|
||||
)
|
||||
if (aChatItem != null) {
|
||||
chatModel.addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem)
|
||||
withChats {
|
||||
addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem)
|
||||
}
|
||||
return aChatItem.chatItem
|
||||
}
|
||||
if (file != null) removeFile(file.filePath)
|
||||
@@ -421,7 +424,9 @@ fun ComposeView(
|
||||
ttl = ttl
|
||||
)
|
||||
if (chatItem != null) {
|
||||
chatModel.addChatItem(rhId, chat.chatInfo, chatItem)
|
||||
withChats {
|
||||
addChatItem(rhId, chat.chatInfo, chatItem)
|
||||
}
|
||||
}
|
||||
return chatItem
|
||||
}
|
||||
@@ -458,7 +463,9 @@ fun ComposeView(
|
||||
val mc = checkLinkPreview()
|
||||
val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc)
|
||||
if (contact != null) {
|
||||
chatModel.updateContact(chat.remoteHostId, contact)
|
||||
withChats {
|
||||
updateContact(chat.remoteHostId, contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +481,9 @@ fun ComposeView(
|
||||
mc = updateMsgContent(oldMsgContent),
|
||||
live = live
|
||||
)
|
||||
if (updatedItem != null) chatModel.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem)
|
||||
if (updatedItem != null) withChats {
|
||||
upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem)
|
||||
}
|
||||
return updatedItem?.chatItem
|
||||
}
|
||||
return null
|
||||
|
||||
+5
-2
@@ -19,6 +19,7 @@ import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.PreferenceToggle
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.platform.ColumnWithScrollBar
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@@ -40,8 +41,10 @@ fun ContactPreferencesView(
|
||||
val prefs = contactFeaturesAllowedToPrefs(featuresAllowed)
|
||||
val toContact = m.controller.apiSetContactPrefs(rhId, ct.contactId, prefs)
|
||||
if (toContact != null) {
|
||||
m.updateContact(rhId, toContact)
|
||||
currentFeaturesAllowed = featuresAllowed
|
||||
withChats {
|
||||
updateContact(rhId, toContact)
|
||||
currentFeaturesAllowed = featuresAllowed
|
||||
}
|
||||
}
|
||||
afterSave()
|
||||
}
|
||||
|
||||
+5
-2
@@ -24,6 +24,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
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.ui.theme.*
|
||||
import chat.simplex.common.views.chat.ChatInfoToolbarTitle
|
||||
import chat.simplex.common.views.helpers.*
|
||||
@@ -58,7 +59,9 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea
|
||||
for (contactId in selectedContacts) {
|
||||
val member = chatModel.controller.apiAddMember(rhId, groupInfo.groupId, contactId, selectedRole.value)
|
||||
if (member != null) {
|
||||
chatModel.upsertGroupMember(rhId, groupInfo, member)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, groupInfo, member)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
@@ -81,7 +84,7 @@ fun getContactsToAdd(chatModel: ChatModel, search: String): List<Contact> {
|
||||
val memberContactIds = chatModel.groupMembers
|
||||
.filter { it.memberCurrent }
|
||||
.mapNotNull { it.memberContactId }
|
||||
return chatModel.chats
|
||||
return chatModel.chats.value
|
||||
.asSequence()
|
||||
.map { it.chatInfo }
|
||||
.filterIsInstance<ChatInfo.Direct>()
|
||||
|
||||
+14
-9
@@ -26,6 +26,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
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.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
@@ -43,7 +44,7 @@ const val SMALL_GROUPS_RCPS_MEM_LIMIT: Int = 20
|
||||
fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLink: String?, groupLinkMemberRole: GroupMemberRole?, onGroupLinkUpdated: (Pair<String, GroupMemberRole>?) -> Unit, close: () -> Unit) {
|
||||
BackHandler(onBack = close)
|
||||
// TODO derivedStateOf?
|
||||
val chat = chatModel.chats.firstOrNull { ch -> ch.id == chatId && ch.remoteHostId == rhId }
|
||||
val chat = chatModel.chats.value.firstOrNull { ch -> ch.id == chatId && ch.remoteHostId == rhId }
|
||||
val currentUser = chatModel.currentUser.value
|
||||
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
||||
if (chat != null && chat.chatInfo is ChatInfo.Group && currentUser != null) {
|
||||
@@ -131,13 +132,15 @@ fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, cl
|
||||
withBGApi {
|
||||
val r = chatModel.controller.apiDeleteChat(chat.remoteHostId, chatInfo.chatType, chatInfo.apiId)
|
||||
if (r) {
|
||||
chatModel.removeChat(chat.remoteHostId, chatInfo.id)
|
||||
if (chatModel.chatId.value == chatInfo.id) {
|
||||
chatModel.chatId.value = null
|
||||
ModalManager.end.closeModals()
|
||||
withChats {
|
||||
removeChat(chat.remoteHostId, chatInfo.id)
|
||||
if (chatModel.chatId.value == chatInfo.id) {
|
||||
chatModel.chatId.value = null
|
||||
ModalManager.end.closeModals()
|
||||
}
|
||||
ntfManager.cancelNotificationsForChat(chatInfo.id)
|
||||
close?.invoke()
|
||||
}
|
||||
ntfManager.cancelNotificationsForChat(chatInfo.id)
|
||||
close?.invoke()
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -169,7 +172,9 @@ private fun removeMemberAlert(rhId: Long?, groupInfo: GroupInfo, mem: GroupMembe
|
||||
withBGApi {
|
||||
val updatedMember = chatModel.controller.apiRemoveMember(rhId, groupInfo.groupId, mem.groupMemberId)
|
||||
if (updatedMember != null) {
|
||||
chatModel.upsertGroupMember(rhId, groupInfo, updatedMember)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, groupInfo, updatedMember)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -235,7 +240,7 @@ fun GroupChatInfoLayout(
|
||||
|
||||
WallpaperButton {
|
||||
ModalManager.end.showModal {
|
||||
val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } }
|
||||
val chat = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chat.id } } }
|
||||
val c = chat.value
|
||||
if (c != null) {
|
||||
ChatWallpaperEditorModal(c)
|
||||
|
||||
+47
-24
@@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chat.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
@@ -52,7 +53,7 @@ fun GroupMemberInfoView(
|
||||
closeAll: () -> Unit, // Close all open windows up to ChatView
|
||||
) {
|
||||
BackHandler(onBack = close)
|
||||
val chat = chatModel.chats.firstOrNull { ch -> ch.id == chatModel.chatId.value && ch.remoteHostId == rhId }
|
||||
val chat = chatModel.chats.value.firstOrNull { ch -> ch.id == chatModel.chatId.value && ch.remoteHostId == rhId }
|
||||
val connStats = remember { mutableStateOf(connectionStats) }
|
||||
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
||||
var progressIndicator by remember { mutableStateOf(false) }
|
||||
@@ -72,13 +73,15 @@ fun GroupMemberInfoView(
|
||||
withBGApi {
|
||||
val c = chatModel.controller.apiGetChat(rhId, ChatType.Direct, it)
|
||||
if (c != null) {
|
||||
if (chatModel.getContactChat(it) == null) {
|
||||
chatModel.addChat(c)
|
||||
withChats {
|
||||
if (chatModel.getContactChat(it) == null) {
|
||||
addChat(c)
|
||||
}
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatItems.replaceAll(c.chatItems)
|
||||
chatModel.chatId.value = c.id
|
||||
closeAll()
|
||||
}
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatItems.replaceAll(c.chatItems)
|
||||
chatModel.chatId.value = c.id
|
||||
closeAll()
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -88,8 +91,10 @@ fun GroupMemberInfoView(
|
||||
val memberContact = chatModel.controller.apiCreateMemberContact(rhId, groupInfo.apiId, member.groupMemberId)
|
||||
if (memberContact != null) {
|
||||
val memberChat = Chat(remoteHostId = rhId, ChatInfo.Direct(memberContact), chatItems = arrayListOf())
|
||||
chatModel.addChat(memberChat)
|
||||
openLoadedChat(memberChat, chatModel)
|
||||
withChats {
|
||||
addChat(memberChat)
|
||||
openLoadedChat(memberChat, chatModel)
|
||||
}
|
||||
closeAll()
|
||||
chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected())
|
||||
}
|
||||
@@ -114,7 +119,9 @@ fun GroupMemberInfoView(
|
||||
withBGApi {
|
||||
kotlin.runCatching {
|
||||
val mem = chatModel.controller.apiMemberRole(rhId, groupInfo.groupId, member.groupMemberId, it)
|
||||
chatModel.upsertGroupMember(rhId, groupInfo, mem)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, groupInfo, mem)
|
||||
}
|
||||
}.onFailure {
|
||||
newRole.value = prevValue
|
||||
}
|
||||
@@ -127,7 +134,9 @@ fun GroupMemberInfoView(
|
||||
val r = chatModel.controller.apiSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId)
|
||||
if (r != null) {
|
||||
connStats.value = r.second
|
||||
chatModel.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
|
||||
withChats {
|
||||
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
}
|
||||
@@ -139,7 +148,9 @@ fun GroupMemberInfoView(
|
||||
val r = chatModel.controller.apiAbortSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId)
|
||||
if (r != null) {
|
||||
connStats.value = r.second
|
||||
chatModel.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
|
||||
withChats {
|
||||
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
}
|
||||
@@ -150,7 +161,9 @@ fun GroupMemberInfoView(
|
||||
val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = false)
|
||||
if (r != null) {
|
||||
connStats.value = r.second
|
||||
chatModel.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
|
||||
withChats {
|
||||
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
}
|
||||
@@ -161,7 +174,9 @@ fun GroupMemberInfoView(
|
||||
val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = true)
|
||||
if (r != null) {
|
||||
connStats.value = r.second
|
||||
chatModel.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
|
||||
withChats {
|
||||
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
}
|
||||
@@ -177,15 +192,17 @@ fun GroupMemberInfoView(
|
||||
verify = { code ->
|
||||
chatModel.controller.apiVerifyGroupMember(rhId, mem.groupId, mem.groupMemberId, code)?.let { r ->
|
||||
val (verified, existingCode) = r
|
||||
chatModel.upsertGroupMember(
|
||||
rhId,
|
||||
groupInfo,
|
||||
mem.copy(
|
||||
activeConn = mem.activeConn?.copy(
|
||||
connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null
|
||||
withChats {
|
||||
upsertGroupMember(
|
||||
rhId,
|
||||
groupInfo,
|
||||
mem.copy(
|
||||
activeConn = mem.activeConn?.copy(
|
||||
connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
r
|
||||
}
|
||||
},
|
||||
@@ -211,7 +228,9 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c
|
||||
withBGApi {
|
||||
val removedMember = chatModel.controller.apiRemoveMember(rhId, member.groupId, member.groupMemberId)
|
||||
if (removedMember != null) {
|
||||
chatModel.upsertGroupMember(rhId, groupInfo, removedMember)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, groupInfo, removedMember)
|
||||
}
|
||||
}
|
||||
close?.invoke()
|
||||
}
|
||||
@@ -621,7 +640,9 @@ fun updateMemberSettings(rhId: Long?, gInfo: GroupInfo, member: GroupMember, mem
|
||||
withBGApi {
|
||||
val success = ChatController.apiSetMemberSettings(rhId, gInfo.groupId, member.groupMemberId, memberSettings)
|
||||
if (success) {
|
||||
ChatModel.upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings))
|
||||
withChats {
|
||||
upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -652,7 +673,9 @@ fun unblockForAllAlert(rhId: Long?, gInfo: GroupInfo, mem: GroupMember) {
|
||||
fun blockMemberForAll(rhId: Long?, gInfo: GroupInfo, member: GroupMember, blocked: Boolean) {
|
||||
withBGApi {
|
||||
val updatedMember = ChatController.apiBlockMemberForAll(rhId, gInfo.groupId, member.groupMemberId, blocked)
|
||||
chatModel.upsertGroupMember(rhId, gInfo, updatedMember)
|
||||
withChats {
|
||||
upsertGroupMember(rhId, gInfo, updatedMember)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -17,6 +17,7 @@ import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.PreferenceToggleWithIcon
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.platform.ColumnWithScrollBar
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
@@ -43,8 +44,10 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () ->
|
||||
val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences())
|
||||
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp)
|
||||
if (g != null) {
|
||||
m.updateGroup(rhId, g)
|
||||
currentPreferences = preferences
|
||||
withChats {
|
||||
updateGroup(rhId, g)
|
||||
currentPreferences = preferences
|
||||
}
|
||||
}
|
||||
afterSave()
|
||||
}
|
||||
|
||||
+4
-1
@@ -17,6 +17,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
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.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.*
|
||||
@@ -38,7 +39,9 @@ fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl
|
||||
withBGApi {
|
||||
val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p)
|
||||
if (gInfo != null) {
|
||||
chatModel.updateGroup(rhId, gInfo)
|
||||
withChats {
|
||||
updateGroup(rhId, gInfo)
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -28,6 +28,7 @@ import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||
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.GroupInfo
|
||||
import chat.simplex.common.platform.ColumnWithScrollBar
|
||||
import chat.simplex.common.platform.chatJsonLength
|
||||
@@ -52,7 +53,9 @@ fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: ()
|
||||
val res = m.controller.apiUpdateGroup(rhId, gInfo.groupId, groupProfileUpdated)
|
||||
if (res != null) {
|
||||
gInfo = res
|
||||
m.updateGroup(rhId, res)
|
||||
withChats {
|
||||
updateGroup(rhId, res)
|
||||
}
|
||||
welcomeText.value = welcome ?: ""
|
||||
}
|
||||
afterSave()
|
||||
|
||||
+31
-10
@@ -19,6 +19,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
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.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chat.*
|
||||
@@ -556,7 +557,9 @@ fun markChatRead(c: Chat, chatModel: ChatModel) {
|
||||
withApi {
|
||||
if (chat.chatStats.unreadCount > 0) {
|
||||
val minUnreadItemId = chat.chatStats.minUnreadItemId
|
||||
chatModel.markChatItemsRead(chat)
|
||||
withChats {
|
||||
markChatItemsRead(chat)
|
||||
}
|
||||
chatModel.controller.apiChatRead(
|
||||
chat.remoteHostId,
|
||||
chat.chatInfo.chatType,
|
||||
@@ -573,7 +576,9 @@ fun markChatRead(c: Chat, chatModel: ChatModel) {
|
||||
false
|
||||
)
|
||||
if (success) {
|
||||
chatModel.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
|
||||
withChats {
|
||||
replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,7 +596,9 @@ fun markChatUnread(chat: Chat, chatModel: ChatModel) {
|
||||
true
|
||||
)
|
||||
if (success) {
|
||||
chatModel.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true)))
|
||||
withChats {
|
||||
replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -631,7 +638,9 @@ fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRe
|
||||
val contact = chatModel.controller.apiAcceptContactRequest(rhId, incognito, apiId)
|
||||
if (contact != null && isCurrentUser && contactRequest != null) {
|
||||
val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf())
|
||||
chatModel.replaceChat(rhId, contactRequest.id, chat)
|
||||
withChats {
|
||||
replaceChat(rhId, contactRequest.id, chat)
|
||||
}
|
||||
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
|
||||
}
|
||||
}
|
||||
@@ -640,7 +649,9 @@ fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRe
|
||||
fun rejectContactRequest(rhId: Long?, contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) {
|
||||
withBGApi {
|
||||
chatModel.controller.apiRejectContactRequest(rhId, contactRequest.apiId)
|
||||
chatModel.removeChat(rhId, contactRequest.id)
|
||||
withChats {
|
||||
removeChat(rhId, contactRequest.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,7 +667,9 @@ fun deleteContactConnectionAlert(rhId: Long?, connection: PendingContactConnecti
|
||||
withBGApi {
|
||||
AlertManager.shared.hideAlert()
|
||||
if (chatModel.controller.apiDeleteChat(rhId, ChatType.ContactConnection, connection.apiId)) {
|
||||
chatModel.removeChat(rhId, connection.id)
|
||||
withChats {
|
||||
removeChat(rhId, connection.id)
|
||||
}
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
@@ -675,7 +688,9 @@ fun pendingContactAlertDialog(rhId: Long?, chatInfo: ChatInfo, chatModel: ChatMo
|
||||
withBGApi {
|
||||
val r = chatModel.controller.apiDeleteChat(rhId, chatInfo.chatType, chatInfo.apiId)
|
||||
if (r) {
|
||||
chatModel.removeChat(rhId, chatInfo.id)
|
||||
withChats {
|
||||
removeChat(rhId, chatInfo.id)
|
||||
}
|
||||
if (chatModel.chatId.value == chatInfo.id) {
|
||||
chatModel.chatId.value = null
|
||||
ModalManager.end.closeModals()
|
||||
@@ -737,7 +752,9 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress(
|
||||
suspend fun connectContactViaAddress(chatModel: ChatModel, rhId: Long?, contactId: Long, incognito: Boolean): Boolean {
|
||||
val contact = chatModel.controller.apiConnectContactViaAddress(rhId, incognito, contactId)
|
||||
if (contact != null) {
|
||||
chatModel.updateContact(rhId, contact)
|
||||
withChats {
|
||||
updateContact(rhId, contact)
|
||||
}
|
||||
AlertManager.privacySensitive.showAlertMsg(
|
||||
title = generalGetString(MR.strings.connection_request_sent),
|
||||
text = generalGetString(MR.strings.you_will_be_connected_when_your_connection_request_is_accepted),
|
||||
@@ -778,7 +795,9 @@ fun deleteGroup(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) {
|
||||
withBGApi {
|
||||
val r = chatModel.controller.apiDeleteChat(rhId, ChatType.Group, groupInfo.apiId)
|
||||
if (r) {
|
||||
chatModel.removeChat(rhId, groupInfo.id)
|
||||
withChats {
|
||||
removeChat(rhId, groupInfo.id)
|
||||
}
|
||||
if (chatModel.chatId.value == groupInfo.id) {
|
||||
chatModel.chatId.value = null
|
||||
ModalManager.end.closeModals()
|
||||
@@ -827,7 +846,9 @@ fun updateChatSettings(chat: Chat, chatSettings: ChatSettings, chatModel: ChatMo
|
||||
else -> false
|
||||
}
|
||||
if (res && newChatInfo != null) {
|
||||
chatModel.updateChatInfo(chat.remoteHostId, newChatInfo)
|
||||
withChats {
|
||||
updateChatInfo(chat.remoteHostId, newChatInfo)
|
||||
}
|
||||
if (chatSettings.enableNtfs != MsgFilter.All) {
|
||||
ntfManager.cancelNotificationsForChat(chat.id)
|
||||
}
|
||||
|
||||
+4
-4
@@ -121,7 +121,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
|
||||
if (!chatModel.desktopNoUserNoRemote) {
|
||||
ChatList(chatModel, searchText = searchText)
|
||||
}
|
||||
if (chatModel.chats.isEmpty() && !chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) {
|
||||
if (chatModel.chats.value.isEmpty() && !chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) {
|
||||
Text(stringResource(
|
||||
if (chatModel.chatRunning.value == null) MR.strings.loading_chats else MR.strings.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary)
|
||||
if (!stopped && !newChatSheetState.collectAsState().value.isVisible() && chatModel.chatRunning.value == true && searchText.value.text.isEmpty()) {
|
||||
@@ -415,7 +415,7 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState
|
||||
}
|
||||
} else {
|
||||
val padding = if (appPlatform.isDesktop) 0.dp else 7.dp
|
||||
if (chatModel.chats.size > 0) {
|
||||
if (chatModel.chats.value.isNotEmpty()) {
|
||||
ToggleFilterEnabledButton()
|
||||
}
|
||||
Spacer(Modifier.width(padding))
|
||||
@@ -494,7 +494,7 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldVal
|
||||
// val chats by remember(search, showUnreadAndFavorites) { derivedStateOf { filteredChats(showUnreadAndFavorites, search, allChats.toList()) } }
|
||||
val searchShowingSimplexLink = remember { mutableStateOf(false) }
|
||||
val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) }
|
||||
val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.toList())
|
||||
val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.value.toList())
|
||||
LazyColumnWithScrollBar(
|
||||
Modifier.fillMaxWidth(),
|
||||
listState
|
||||
@@ -523,7 +523,7 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldVal
|
||||
ChatListNavLinkView(chat, nextChatSelected)
|
||||
}
|
||||
}
|
||||
if (chats.isEmpty() && chatModel.chats.isNotEmpty()) {
|
||||
if (chats.isEmpty() && chatModel.chats.value.isNotEmpty()) {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(generalGetString(MR.strings.no_filtered_chats), color = MaterialTheme.colors.secondary)
|
||||
}
|
||||
|
||||
+3
-3
@@ -61,7 +61,7 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
if (chatModel.chats.isNotEmpty()) {
|
||||
if (chatModel.chats.value.isNotEmpty()) {
|
||||
ShareList(
|
||||
chatModel,
|
||||
search = searchInList,
|
||||
@@ -127,7 +127,7 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState
|
||||
})
|
||||
}
|
||||
}
|
||||
if (chatModel.chats.size >= 8) {
|
||||
if (chatModel.chats.value.size >= 8) {
|
||||
barButtons.add {
|
||||
IconButton({ showSearch = true }) {
|
||||
Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary)
|
||||
@@ -186,7 +186,7 @@ private fun ShareList(
|
||||
) {
|
||||
val chats by remember(search) {
|
||||
derivedStateOf {
|
||||
val sorted = chatModel.chats.toList().sortedByDescending { it.chatInfo is ChatInfo.Local }
|
||||
val sorted = chatModel.chats.value.toList().sortedByDescending { it.chatInfo is ChatInfo.Local }
|
||||
if (search.isEmpty()) {
|
||||
sorted.filter { it.chatInfo.ready }
|
||||
} else {
|
||||
|
||||
+8
-4
@@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp
|
||||
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.updatingChatsMutex
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
@@ -502,7 +502,11 @@ fun deleteChatDatabaseFilesAndState() {
|
||||
// Clear sensitive data on screen just in case ModalManager will fail to prevent hiding its modals while database encrypts itself
|
||||
chatModel.chatId.value = null
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chats.clear()
|
||||
withLongRunningApi {
|
||||
withChats {
|
||||
chats.clear()
|
||||
}
|
||||
}
|
||||
chatModel.users.clear()
|
||||
ntfManager.cancelAllNotifications()
|
||||
}
|
||||
@@ -714,10 +718,10 @@ private fun afterSetCiTTL(
|
||||
appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath)
|
||||
withApi {
|
||||
try {
|
||||
updatingChatsMutex.withLock {
|
||||
withChats {
|
||||
// this is using current remote host on purpose - if it changes during update, it will load correct chats
|
||||
val chats = m.controller.apiGetChats(m.remoteHostId())
|
||||
m.updateChats(chats)
|
||||
updateChats(chats)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "apiGetChats error: ${e.message}")
|
||||
|
||||
+7
-4
@@ -18,6 +18,7 @@ import dev.icerock.moko.resources.compose.stringResource
|
||||
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.ui.theme.*
|
||||
import chat.simplex.common.views.chat.group.AddGroupMembersView
|
||||
import chat.simplex.common.views.chatlist.setGroupMembers
|
||||
@@ -39,10 +40,12 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) {
|
||||
withBGApi {
|
||||
val groupInfo = chatModel.controller.apiNewGroup(rhId, incognito, groupProfile)
|
||||
if (groupInfo != null) {
|
||||
chatModel.updateGroup(rhId = rhId, groupInfo)
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatId.value = groupInfo.id
|
||||
withChats {
|
||||
updateGroup(rhId = rhId, groupInfo)
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatId.value = groupInfo.id
|
||||
}
|
||||
setGroupMembers(rhId, groupInfo, chatModel)
|
||||
close.invoke()
|
||||
if (!groupInfo.incognito) {
|
||||
|
||||
+4
-1
@@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.chatlist.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
@@ -331,7 +332,9 @@ suspend fun connectViaUri(
|
||||
val pcc = chatModel.controller.apiConnect(rhId, incognito, uri.toString())
|
||||
val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION
|
||||
if (pcc != null) {
|
||||
chatModel.updateContactConnection(rhId, pcc)
|
||||
withChats {
|
||||
updateContactConnection(rhId, pcc)
|
||||
}
|
||||
close?.invoke()
|
||||
AlertManager.privacySensitive.showAlertMsg(
|
||||
title = generalGetString(MR.strings.connection_request_sent),
|
||||
|
||||
+4
-1
@@ -22,6 +22,7 @@ import chat.simplex.common.views.chat.LocalAliasEditor
|
||||
import chat.simplex.common.views.chatlist.deleteContactConnectionAlert
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.model.PendingContactConnection
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
@@ -186,7 +187,9 @@ fun DeleteButton(onClick: () -> Unit) {
|
||||
|
||||
private fun setContactAlias(rhId: Long?, contactConnection: PendingContactConnection, localAlias: String, chatModel: ChatModel) = withBGApi {
|
||||
chatModel.controller.apiSetConnectionAlias(rhId, contactConnection.pccConnId, localAlias)?.let {
|
||||
chatModel.updateContactConnection(rhId, it)
|
||||
withChats {
|
||||
updateContactConnection(rhId, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-5
@@ -25,6 +25,7 @@ import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
@@ -218,8 +219,10 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection
|
||||
withBGApi {
|
||||
val contactConn = contactConnection.value ?: return@withBGApi
|
||||
val conn = controller.apiSetConnectionIncognito(rhId, contactConn.pccConnId, incognito.value) ?: return@withBGApi
|
||||
contactConnection.value = conn
|
||||
chatModel.updateContactConnection(rhId, conn)
|
||||
withChats {
|
||||
contactConnection.value = conn
|
||||
updateContactConnection(rhId, conn)
|
||||
}
|
||||
}
|
||||
chatModel.markShowingInvitationUsed()
|
||||
}
|
||||
@@ -367,9 +370,11 @@ private fun createInvitation(
|
||||
withBGApi {
|
||||
val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get())
|
||||
if (r != null) {
|
||||
chatModel.updateContactConnection(rhId, r.second)
|
||||
chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false)
|
||||
contactConnection.value = r.second
|
||||
withChats {
|
||||
updateContactConnection(rhId, r.second)
|
||||
chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false)
|
||||
contactConnection.value = r.second
|
||||
}
|
||||
} else {
|
||||
creatingConnReq.value = false
|
||||
if (alert != null) {
|
||||
|
||||
+4
-1
@@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.platform.ColumnWithScrollBar
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@@ -33,7 +34,9 @@ fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) {
|
||||
if (updated != null) {
|
||||
val (updatedProfile, updatedContacts) = updated
|
||||
m.updateCurrentUser(user.remoteHostId, updatedProfile, preferences)
|
||||
updatedContacts.forEach { m.updateContact(user.remoteHostId, it) }
|
||||
withChats {
|
||||
updatedContacts.forEach { updateContact(user.remoteHostId, it) }
|
||||
}
|
||||
currentPreferences = preferences
|
||||
}
|
||||
afterSave()
|
||||
|
||||
+24
-19
@@ -32,6 +32,7 @@ import chat.simplex.common.views.isValidDisplayName
|
||||
import chat.simplex.common.views.localauth.SetAppPasscodeView
|
||||
import chat.simplex.common.views.onboarding.ReadableText
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.platform.*
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
@@ -121,14 +122,16 @@ fun PrivacySettingsView(
|
||||
chatModel.currentUser.value = currentUser.copy(sendRcptsContacts = enable)
|
||||
if (clearOverrides) {
|
||||
// For loop here is to prevent ConcurrentModificationException that happens with forEach
|
||||
for (i in 0 until chatModel.chats.size) {
|
||||
val chat = chatModel.chats[i]
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
var contact = chat.chatInfo.contact
|
||||
val sendRcpts = contact.chatSettings.sendRcpts
|
||||
if (sendRcpts != null && sendRcpts != enable) {
|
||||
contact = contact.copy(chatSettings = contact.chatSettings.copy(sendRcpts = null))
|
||||
chatModel.updateContact(currentUser.remoteHostId, contact)
|
||||
withChats {
|
||||
for (i in 0 until chats.size) {
|
||||
val chat = chats[i]
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
var contact = chat.chatInfo.contact
|
||||
val sendRcpts = contact.chatSettings.sendRcpts
|
||||
if (sendRcpts != null && sendRcpts != enable) {
|
||||
contact = contact.copy(chatSettings = contact.chatSettings.copy(sendRcpts = null))
|
||||
updateContact(currentUser.remoteHostId, contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,15 +146,17 @@ fun PrivacySettingsView(
|
||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
chatModel.currentUser.value = currentUser.copy(sendRcptsSmallGroups = enable)
|
||||
if (clearOverrides) {
|
||||
// For loop here is to prevent ConcurrentModificationException that happens with forEach
|
||||
for (i in 0 until chatModel.chats.size) {
|
||||
val chat = chatModel.chats[i]
|
||||
if (chat.chatInfo is ChatInfo.Group) {
|
||||
var groupInfo = chat.chatInfo.groupInfo
|
||||
val sendRcpts = groupInfo.chatSettings.sendRcpts
|
||||
if (sendRcpts != null && sendRcpts != enable) {
|
||||
groupInfo = groupInfo.copy(chatSettings = groupInfo.chatSettings.copy(sendRcpts = null))
|
||||
chatModel.updateGroup(currentUser.remoteHostId, groupInfo)
|
||||
withChats {
|
||||
// For loop here is to prevent ConcurrentModificationException that happens with forEach
|
||||
for (i in 0 until chats.size) {
|
||||
val chat = chats[i]
|
||||
if (chat.chatInfo is ChatInfo.Group) {
|
||||
var groupInfo = chat.chatInfo.groupInfo
|
||||
val sendRcpts = groupInfo.chatSettings.sendRcpts
|
||||
if (sendRcpts != null && sendRcpts != enable) {
|
||||
groupInfo = groupInfo.copy(chatSettings = groupInfo.chatSettings.copy(sendRcpts = null))
|
||||
updateGroup(currentUser.remoteHostId, groupInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,7 +169,7 @@ fun PrivacySettingsView(
|
||||
DeliveryReceiptsSection(
|
||||
currentUser = currentUser,
|
||||
setOrAskSendReceiptsContacts = { enable ->
|
||||
val contactReceiptsOverrides = chatModel.chats.fold(0) { count, chat ->
|
||||
val contactReceiptsOverrides = chatModel.chats.value.fold(0) { count, chat ->
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
val sendRcpts = chat.chatInfo.contact.chatSettings.sendRcpts
|
||||
count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
|
||||
@@ -179,7 +184,7 @@ fun PrivacySettingsView(
|
||||
}
|
||||
},
|
||||
setOrAskSendReceiptsGroups = { enable ->
|
||||
val groupReceiptsOverrides = chatModel.chats.fold(0) { count, chat ->
|
||||
val groupReceiptsOverrides = chatModel.chats.value.fold(0) { count, chat ->
|
||||
if (chat.chatInfo is ChatInfo.Group) {
|
||||
val sendRcpts = chat.chatInfo.groupInfo.chatSettings.sendRcpts
|
||||
count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.size
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.platform.DesktopPlatform
|
||||
import chat.simplex.common.showApp
|
||||
|
||||
Reference in New Issue
Block a user