kotlin: refactor chat contexts 1 (remove functions creating indirection) (#5827)

* kotlin: refactor chat contexts 1

* remove withChats

* comment

* remove withReportChatsIfOpen

* remove comment

* fix desktop
This commit is contained in:
spaced4ndy 2025-04-14 16:01:22 +00:00 committed by GitHub
parent 14d9240995
commit 38c2529d8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 598 additions and 580 deletions

View file

@ -24,7 +24,6 @@ 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.withChats
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.call.*
@ -33,7 +32,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.flow.map
import java.io.*
import java.util.*
import java.util.concurrent.TimeUnit
@ -94,7 +92,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
Lifecycle.Event.ON_START -> {
isAppOnForeground = true
if (chatModel.chatRunning.value == true) {
withChats {
withContext(Dispatchers.Main) {
kotlin.runCatching {
val currentUserId = chatModel.currentUser.value?.userId
val chats = ArrayList(chatController.apiGetChats(chatModel.remoteHostId()))
@ -107,7 +105,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)
}
updateChats(chats)
chatModel.chatsContext.updateChats(chats)
}
}.onFailure { Log.e(TAG, it.stackTraceToString()) }
}

View file

@ -14,12 +14,11 @@ import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import chat.simplex.common.AppScreen
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.clear
import chat.simplex.common.model.clearAndNotify
import chat.simplex.common.views.helpers.*
import androidx.compose.ui.platform.LocalContext as LocalContext1
import chat.simplex.res.MR
import kotlinx.coroutines.*
actual fun showToast(text: String, timeout: Long) = Toast.makeText(androidAppContext, text, Toast.LENGTH_SHORT).show()
@ -76,13 +75,13 @@ actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
ModalManager.start.closeModal()
} else if (chatModel.chatId.value != null) {
withApi {
withChats {
withContext(Dispatchers.Main) {
// Since no modals are open, the problem is probably in ChatView
chatModel.chatId.value = null
chatItems.clearAndNotify()
chatModel.chatsContext.chatItems.clearAndNotify()
}
withChats {
chatItems.clearAndNotify()
withContext(Dispatchers.Main) {
chatModel.chatsContext.chatItems.clearAndNotify()
}
}
} else {

View file

@ -67,7 +67,7 @@ object ChatModel {
val chatId = mutableStateOf<String?>(null)
val openAroundItemId: MutableState<Long?> = mutableStateOf(null)
val chatsContext = ChatsContext(null)
val reportsChatsContext = ChatsContext(MsgContentTag.Report)
val secondaryChatsContext = ChatsContext(MsgContentTag.Report)
// declaration of chatsContext should be before any other variable that is taken from ChatsContext class and used in the model, otherwise, strange crash with NullPointerException for "this" parameter in random functions
val chats: State<List<Chat>> = chatsContext.chats
// rhId, chatId
@ -170,36 +170,6 @@ object ChatModel {
// return true if you handled the click
var centerPanelBackgroundClickHandler: (() -> Boolean)? = null
fun chatsForContent(contentTag: MsgContentTag?): State<SnapshotStateList<Chat>> = when(contentTag) {
null -> chatsContext.chats
MsgContentTag.Report -> reportsChatsContext.chats
else -> TODO()
}
fun chatItemsForContent(contentTag: MsgContentTag?): State<SnapshotStateList<ChatItem>> = when(contentTag) {
null -> chatsContext.chatItems
MsgContentTag.Report -> reportsChatsContext.chatItems
else -> TODO()
}
fun chatStateForContent(contentTag: MsgContentTag?): ActiveChatState = when(contentTag) {
null -> chatsContext.chatState
MsgContentTag.Report -> reportsChatsContext.chatState
else -> TODO()
}
fun chatItemsChangesListenerForContent(contentTag: MsgContentTag?): ChatItemsChangesListener? = when(contentTag) {
null -> chatsContext.chatItemsChangesListener
MsgContentTag.Report -> reportsChatsContext.chatItemsChangesListener
else -> TODO()
}
fun setChatItemsChangeListenerForContent(listener: ChatItemsChangesListener?, contentTag: MsgContentTag?) = when(contentTag) {
null -> chatsContext.chatItemsChangesListener = listener
MsgContentTag.Report -> reportsChatsContext.chatItemsChangesListener = listener
else -> TODO()
}
fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
currentUser.value
} else {
@ -324,21 +294,6 @@ object ChatModel {
}
}
// running everything inside the block on main thread. Make sure any heavy computation is moved to a background thread
suspend fun <T> withChats(contentTag: MsgContentTag? = null, action: suspend ChatsContext.() -> T): T = withContext(Dispatchers.Main) {
when {
contentTag == null -> chatsContext.action()
contentTag == MsgContentTag.Report -> reportsChatsContext.action()
else -> TODO()
}
}
suspend fun <T> withReportsChatsIfOpen(action: suspend ChatsContext.() -> T) = withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) {
reportsChatsContext.action()
}
}
class ChatsContext(private val contentTag: MsgContentTag?) {
val chats = mutableStateOf(SnapshotStateList<Chat>())
/** if you modify the items by adding/removing them, use helpers methods like [addAndNotify], [removeLastAndNotify], [removeAllAndNotify], [clearAndNotify] and so on.
@ -659,8 +614,9 @@ object ChatModel {
subject
.throttleLatest(2000)
.collect {
withChats(contentTag) {
chats.replaceAll(popCollectedChats())
withContext(Dispatchers.Main) {
val chatsCtx = if (contentTag == null) chatsContext else secondaryChatsContext
chatsCtx.chats.replaceAll(popCollectedChats())
}
}
}
@ -960,17 +916,17 @@ object ChatModel {
suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem {
val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct)
withChats {
chatItems.addAndNotify(cItem, contentTag = null)
withContext(Dispatchers.Main) {
chatsContext.chatItems.addAndNotify(cItem, contentTag = null)
}
return cItem
}
fun removeLiveDummy() {
if (chatItemsForContent(null).value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
if (chatsContext.chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
withApi {
withChats {
chatItems.removeLastAndNotify(contentTag = null)
withContext(Dispatchers.Main) {
chatsContext.chatItems.removeLastAndNotify(contentTag = null)
}
}
}
@ -1042,9 +998,11 @@ object ChatModel {
fun replaceConnReqView(id: String, withId: String) {
if (id == showingInvitation.value?.connId) {
withApi {
withChats {
withContext(Dispatchers.Main) {
showingInvitation.value = null
chatItems.clearAndNotify()
// TODO [contexts] - why does clearAndNotify operates with listeners for both contexts?
// TODO - should it be called for both contexts here instead?
chatsContext.chatItems.clearAndNotify()
chatModel.chatId.value = withId
}
}
@ -1055,9 +1013,10 @@ object ChatModel {
fun dismissConnReqView(id: String) = withApi {
if (id == showingInvitation.value?.connId) {
withChats {
withContext(Dispatchers.Main) {
showingInvitation.value = null
chatItems.clearAndNotify()
// TODO [contexts] see replaceConnReqView
chatsContext.chatItems.clearAndNotify()
chatModel.chatId.value = null
}
// Close NewChatView
@ -2739,7 +2698,8 @@ fun MutableState<SnapshotStateList<Chat>>.add(index: Int, elem: Chat) {
}
fun MutableState<SnapshotStateList<ChatItem>>.addAndNotify(index: Int, elem: ChatItem, contentTag: MsgContentTag?) {
value = SnapshotStateList<ChatItem>().apply { addAll(value); add(index, elem); chatModel.chatItemsChangesListenerForContent(contentTag)?.added(elem.id to elem.isRcvNew, index) }
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
value = SnapshotStateList<ChatItem>().apply { addAll(value); add(index, elem); chatsCtx.chatItemsChangesListener?.added(elem.id to elem.isRcvNew, index) }
}
fun MutableState<SnapshotStateList<Chat>>.add(elem: Chat) {
@ -2751,7 +2711,8 @@ fun <T> MutableList<T>.removeAll(predicate: (T) -> Boolean): Boolean = if (isEmp
// Adds item to chatItems and notifies a listener about newly added item
fun MutableState<SnapshotStateList<ChatItem>>.addAndNotify(elem: ChatItem, contentTag: MsgContentTag?) {
value = SnapshotStateList<ChatItem>().apply { addAll(value); add(elem); chatModel.chatItemsChangesListenerForContent(contentTag)?.added(elem.id to elem.isRcvNew, lastIndex) }
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
value = SnapshotStateList<ChatItem>().apply { addAll(value); add(elem); chatsCtx.chatItemsChangesListener?.added(elem.id to elem.isRcvNew, lastIndex) }
}
fun <T> MutableState<SnapshotStateList<T>>.addAll(index: Int, elems: List<T>) {
@ -2781,7 +2742,7 @@ fun MutableState<SnapshotStateList<ChatItem>>.removeAllAndNotify(block: (ChatIte
}
if (toRemove.isNotEmpty()) {
chatModel.chatsContext.chatItemsChangesListener?.removed(toRemove, value)
chatModel.reportsChatsContext.chatItemsChangesListener?.removed(toRemove, value)
chatModel.secondaryChatsContext.chatItemsChangesListener?.removed(toRemove, value)
}
}
@ -2801,7 +2762,8 @@ fun MutableState<SnapshotStateList<ChatItem>>.removeLastAndNotify(contentTag: Ms
val rem = removeLast()
removed = Triple(rem.id, remIndex, rem.isRcvNew)
}
chatModel.chatItemsChangesListenerForContent(contentTag)?.removed(listOf(removed), value)
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
chatsCtx.chatItemsChangesListener?.removed(listOf(removed), value)
}
fun <T> MutableState<SnapshotStateList<T>>.replaceAll(elems: List<T>) {
@ -2816,7 +2778,7 @@ fun MutableState<SnapshotStateList<Chat>>.clear() {
fun MutableState<SnapshotStateList<ChatItem>>.clearAndNotify() {
value = SnapshotStateList()
chatModel.chatsContext.chatItemsChangesListener?.cleared()
chatModel.reportsChatsContext.chatItemsChangesListener?.cleared()
chatModel.secondaryChatsContext.chatItemsChangesListener?.cleared()
}
fun <T> State<SnapshotStateList<T>>.asReversed(): MutableList<T> = value.asReversed()

View file

@ -17,8 +17,6 @@ import androidx.compose.ui.unit.dp
import chat.simplex.common.model.ChatController.getNetCfg
import chat.simplex.common.model.ChatController.setNetCfg
import chat.simplex.common.model.ChatModel.changingActiveUserMutex
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.model.MsgContent.MCUnknown
import dev.icerock.moko.resources.compose.painterResource
import chat.simplex.common.platform.*
@ -544,9 +542,9 @@ object ChatController {
}
Log.d(TAG, "startChat: started")
} else {
withChats {
withContext(Dispatchers.Main) {
val chats = apiGetChats(null)
updateChats(chats)
chatModel.chatsContext.updateChats(chats)
}
Log.d(TAG, "startChat: running")
}
@ -627,9 +625,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
withChats {
withContext(Dispatchers.Main) {
val chats = apiGetChats(rhId)
updateChats(chats)
chatModel.chatsContext.updateChats(chats)
}
chatModel.userTags.value = apiGetChatTags(rhId).takeIf { hasUser } ?: emptyList()
chatModel.activeChatTagFilter.value = null
@ -1490,8 +1488,8 @@ object ChatController {
suspend fun deleteChat(chat: Chat, chatDeleteMode: ChatDeleteMode = ChatDeleteMode.Full(notify = true)) {
val cInfo = chat.chatInfo
if (apiDeleteChat(rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, chatDeleteMode = chatDeleteMode)) {
withChats {
removeChat(chat.remoteHostId, cInfo.id)
withContext(Dispatchers.Main) {
chatModel.chatsContext.removeChat(chat.remoteHostId, cInfo.id)
}
}
}
@ -1539,11 +1537,11 @@ object ChatController {
withBGApi {
val updatedChatInfo = apiClearChat(chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId)
if (updatedChatInfo != null) {
withChats {
clearChat(chat.remoteHostId, updatedChatInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.clearChat(chat.remoteHostId, updatedChatInfo)
}
withChats(MsgContentTag.Report) {
clearChat(chat.remoteHostId, updatedChatInfo)
withContext(Dispatchers.Main) {
chatModel.secondaryChatsContext.clearChat(chat.remoteHostId, updatedChatInfo)
}
ntfManager.cancelNotificationsForChat(chat.chatInfo.id)
close?.invoke()
@ -1975,12 +1973,14 @@ object ChatController {
val r = sendCmd(rh, CC.ApiJoinGroup(groupId))
when (r) {
is CR.UserAcceptedGroupSent ->
withChats {
updateGroup(rh, r.groupInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rh, r.groupInfo)
}
is CR.ChatCmdError -> {
val e = r.chatError
suspend fun deleteGroup() { if (apiDeleteChat(rh, ChatType.Group, groupId)) { withChats { removeChat(rh, "#$groupId") } } }
suspend fun deleteGroup() { if (apiDeleteChat(rh, ChatType.Group, groupId)) {
withContext(Dispatchers.Main) { chatModel.chatsContext.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))
@ -2134,8 +2134,8 @@ object ChatController {
val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature, param = param)
val toContact = apiSetContactPrefs(rh, contact.contactId, prefs)
if (toContact != null) {
withChats {
updateContact(rh, toContact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rh, toContact)
}
}
}
@ -2406,19 +2406,19 @@ object ChatController {
when (r) {
is CR.ContactDeletedByContact -> {
if (active(r.user) && r.contact.directOrUsed) {
withChats {
updateContact(rhId, r.contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, r.contact)
}
}
}
is CR.ContactConnected -> {
if (active(r.user) && r.contact.directOrUsed) {
withChats {
updateContact(rhId, r.contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, r.contact)
val conn = r.contact.activeConn
if (conn != null) {
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
removeChat(rhId, conn.id)
chatModel.chatsContext.removeChat(rhId, conn.id)
}
}
}
@ -2429,24 +2429,24 @@ object ChatController {
}
is CR.ContactConnecting -> {
if (active(r.user) && r.contact.directOrUsed) {
withChats {
updateContact(rhId, r.contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, r.contact)
val conn = r.contact.activeConn
if (conn != null) {
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
removeChat(rhId, conn.id)
chatModel.chatsContext.removeChat(rhId, conn.id)
}
}
}
}
is CR.ContactSndReady -> {
if (active(r.user) && r.contact.directOrUsed) {
withChats {
updateContact(rhId, r.contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, r.contact)
val conn = r.contact.activeConn
if (conn != null) {
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
removeChat(rhId, conn.id)
chatModel.chatsContext.removeChat(rhId, conn.id)
}
}
}
@ -2456,11 +2456,11 @@ object ChatController {
val contactRequest = r.contactRequest
val cInfo = ChatInfo.ContactRequest(contactRequest)
if (active(r.user)) {
withChats {
if (hasChat(rhId, contactRequest.id)) {
updateChatInfo(rhId, cInfo)
withContext(Dispatchers.Main) {
if (chatModel.chatsContext.hasChat(rhId, contactRequest.id)) {
chatModel.chatsContext.updateChatInfo(rhId, cInfo)
} else {
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf()))
chatModel.chatsContext.addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf()))
}
}
}
@ -2469,18 +2469,20 @@ object ChatController {
is CR.ContactUpdated -> {
if (active(r.user) && chatModel.chatsContext.hasChat(rhId, r.toContact.id)) {
val cInfo = ChatInfo.Direct(r.toContact)
withChats {
updateChatInfo(rhId, cInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatInfo(rhId, cInfo)
}
}
}
is CR.GroupMemberUpdated -> {
if (active(r.user)) {
withChats {
upsertGroupMember(rhId, r.groupInfo, r.toMember)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.toMember)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, r.groupInfo, r.toMember)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.toMember)
}
}
}
}
@ -2489,8 +2491,8 @@ object ChatController {
if (chatModel.chatId.value == r.mergedContact.id) {
chatModel.chatId.value = r.intoContact.id
}
withChats {
removeChat(rhId, r.mergedContact.id)
withContext(Dispatchers.Main) {
chatModel.chatsContext.removeChat(rhId, r.mergedContact.id)
}
}
}
@ -2501,8 +2503,8 @@ object ChatController {
is CR.ContactSubSummary -> {
for (sub in r.contactSubscriptions) {
if (active(r.user)) {
withChats {
updateContact(rhId, sub.contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, sub.contact)
}
}
val err = sub.contactError
@ -2528,20 +2530,22 @@ object ChatController {
val cInfo = chatItem.chatInfo
val cItem = chatItem.chatItem
if (active(r.user)) {
withChats {
addChatItem(rhId, cInfo, cItem)
withContext(Dispatchers.Main) {
chatModel.chatsContext.addChatItem(rhId, cInfo, cItem)
if (cItem.isActiveReport) {
increaseGroupReportsCounter(rhId, cInfo.id)
chatModel.chatsContext.increaseGroupReportsCounter(rhId, cInfo.id)
}
}
withReportsChatsIfOpen {
if (cItem.isReport) {
addChatItem(rhId, cInfo, cItem)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
if (cItem.isReport) {
chatModel.secondaryChatsContext.addChatItem(rhId, cInfo, cItem)
}
}
}
} else if (cItem.isRcvNew && cInfo.ntfsEnabled(cItem)) {
withChats {
increaseUnreadCounter(rhId, r.user)
withContext(Dispatchers.Main) {
chatModel.chatsContext.increaseUnreadCounter(rhId, r.user)
}
}
val file = cItem.file
@ -2562,12 +2566,14 @@ object ChatController {
val cInfo = chatItem.chatInfo
val cItem = chatItem.chatItem
if (!cItem.isDeletedContent && active(r.user)) {
withChats {
updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
}
withReportsChatsIfOpen {
if (cItem.isReport) {
updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
if (cItem.isReport) {
chatModel.secondaryChatsContext.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
}
}
}
}
@ -2576,12 +2582,14 @@ object ChatController {
chatItemUpdateNotify(rhId, r.user, r.chatItem)
is CR.ChatItemReaction -> {
if (active(r.user)) {
withChats {
updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
}
withReportsChatsIfOpen {
if (r.reaction.chatReaction.chatItem.isReport) {
updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
if (r.reaction.chatReaction.chatItem.isReport) {
chatModel.secondaryChatsContext.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
}
}
}
}
@ -2590,8 +2598,8 @@ object ChatController {
if (!active(r.user)) {
r.chatItemDeletions.forEach { (deletedChatItem, toChatItem) ->
if (toChatItem == null && deletedChatItem.chatItem.isRcvNew && deletedChatItem.chatInfo.ntfsEnabled(deletedChatItem.chatItem)) {
withChats {
decreaseUnreadCounter(rhId, r.user)
withContext(Dispatchers.Main) {
chatModel.chatsContext.decreaseUnreadCounter(rhId, r.user)
}
}
}
@ -2614,22 +2622,24 @@ object ChatController {
generalGetString(if (toChatItem != null) MR.strings.marked_deleted_description else MR.strings.deleted_description)
)
}
withChats {
withContext(Dispatchers.Main) {
if (toChatItem == null) {
removeChatItem(rhId, cInfo, cItem)
chatModel.chatsContext.removeChatItem(rhId, cInfo, cItem)
} else {
upsertChatItem(rhId, cInfo, toChatItem.chatItem)
chatModel.chatsContext.upsertChatItem(rhId, cInfo, toChatItem.chatItem)
}
if (cItem.isActiveReport) {
decreaseGroupReportsCounter(rhId, cInfo.id)
chatModel.chatsContext.decreaseGroupReportsCounter(rhId, cInfo.id)
}
}
withReportsChatsIfOpen {
if (cItem.isReport) {
if (toChatItem == null) {
removeChatItem(rhId, cInfo, cItem)
} else {
upsertChatItem(rhId, cInfo, toChatItem.chatItem)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
if (cItem.isReport) {
if (toChatItem == null) {
chatModel.secondaryChatsContext.removeChatItem(rhId, cInfo, cItem)
} else {
chatModel.secondaryChatsContext.upsertChatItem(rhId, cInfo, toChatItem.chatItem)
}
}
}
}
@ -2640,9 +2650,9 @@ object ChatController {
}
is CR.ReceivedGroupInvitation -> {
if (active(r.user)) {
withChats {
withContext(Dispatchers.Main) {
// update so that repeat group invitations are not duplicated
updateGroup(rhId, r.groupInfo)
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
}
// TODO NtfManager.shared.notifyGroupInvitation
}
@ -2650,137 +2660,149 @@ object ChatController {
is CR.UserAcceptedGroupSent -> {
if (!active(r.user)) return
withChats {
updateGroup(rhId, r.groupInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
val conn = r.hostContact?.activeConn
if (conn != null) {
chatModel.replaceConnReqView(conn.id, "#${r.groupInfo.groupId}")
removeChat(rhId, conn.id)
chatModel.chatsContext.removeChat(rhId, conn.id)
}
}
}
is CR.GroupLinkConnecting -> {
if (!active(r.user)) return
withChats {
updateGroup(rhId, r.groupInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
val hostConn = r.hostMember.activeConn
if (hostConn != null) {
chatModel.replaceConnReqView(hostConn.id, "#${r.groupInfo.groupId}")
removeChat(rhId, hostConn.id)
chatModel.chatsContext.removeChat(rhId, hostConn.id)
}
}
}
is CR.BusinessLinkConnecting -> {
if (!active(r.user)) return
withChats {
updateGroup(rhId, r.groupInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
}
if (chatModel.chatId.value == r.fromContact.id) {
openGroupChat(rhId, r.groupInfo.groupId)
}
withChats {
removeChat(rhId, r.fromContact.id)
withContext(Dispatchers.Main) {
chatModel.chatsContext.removeChat(rhId, r.fromContact.id)
}
}
is CR.JoinedGroupMemberConnecting ->
if (active(r.user)) {
withChats {
upsertGroupMember(rhId, r.groupInfo, r.member)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
}
is CR.DeletedMemberUser -> // TODO update user member
if (active(r.user)) {
withChats {
updateGroup(rhId, r.groupInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
if (r.withMessages) {
removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo)
chatModel.chatsContext.removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo)
}
}
withReportsChatsIfOpen {
if (r.withMessages) {
removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
if (r.withMessages) {
chatModel.secondaryChatsContext.removeMemberItems(rhId, r.groupInfo.membership, byMember = r.member, r.groupInfo)
}
}
}
}
is CR.DeletedMember ->
if (active(r.user)) {
withChats {
upsertGroupMember(rhId, r.groupInfo, r.deletedMember)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.deletedMember)
if (r.withMessages) {
removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo)
chatModel.chatsContext.removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo)
}
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, r.groupInfo, r.deletedMember)
if (r.withMessages) {
removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.deletedMember)
if (r.withMessages) {
chatModel.secondaryChatsContext.removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo)
}
}
}
}
is CR.LeftMember ->
if (active(r.user)) {
withChats {
upsertGroupMember(rhId, r.groupInfo, r.member)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, r.groupInfo, r.member)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
}
}
is CR.MemberRole ->
if (active(r.user)) {
withChats {
upsertGroupMember(rhId, r.groupInfo, r.member)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, r.groupInfo, r.member)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
}
}
is CR.MembersRoleUser ->
if (active(r.user)) {
withChats {
withContext(Dispatchers.Main) {
r.members.forEach { member ->
upsertGroupMember(rhId, r.groupInfo, member)
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, member)
}
}
withReportsChatsIfOpen {
r.members.forEach { member ->
upsertGroupMember(rhId, r.groupInfo, member)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
r.members.forEach { member ->
chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, member)
}
}
}
}
is CR.MemberBlockedForAll ->
if (active(r.user)) {
withChats {
upsertGroupMember(rhId, r.groupInfo, r.member)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, r.groupInfo, r.member)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
}
}
is CR.GroupDeleted -> // TODO update user member
if (active(r.user)) {
withChats {
updateGroup(rhId, r.groupInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
}
}
is CR.UserJoinedGroup ->
if (active(r.user)) {
withChats {
updateGroup(rhId, r.groupInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
}
}
is CR.JoinedGroupMember ->
if (active(r.user)) {
withChats {
upsertGroupMember(rhId, r.groupInfo, r.member)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
}
is CR.ConnectedToGroupMember -> {
if (active(r.user)) {
withChats {
upsertGroupMember(rhId, r.groupInfo, r.member)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
}
if (r.memberContact != null) {
@ -2789,14 +2811,14 @@ object ChatController {
}
is CR.GroupUpdated ->
if (active(r.user)) {
withChats {
updateGroup(rhId, r.toGroup)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.toGroup)
}
}
is CR.NewMemberContactReceivedInv ->
if (active(r.user)) {
withChats {
updateContact(rhId, r.contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, r.contact)
}
}
is CR.RcvFileStart ->
@ -2897,26 +2919,26 @@ object ChatController {
}
is CR.ContactSwitch ->
if (active(r.user)) {
withChats {
updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats)
}
}
is CR.GroupMemberSwitch ->
if (active(r.user)) {
withChats {
updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.switchProgress.connectionStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.switchProgress.connectionStats)
}
}
is CR.ContactRatchetSync ->
if (active(r.user)) {
withChats {
updateContactConnectionStats(rhId, r.contact, r.ratchetSyncProgress.connectionStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnectionStats(rhId, r.contact, r.ratchetSyncProgress.connectionStats)
}
}
is CR.GroupMemberRatchetSync ->
if (active(r.user)) {
withChats {
updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats)
}
}
is CR.RemoteHostSessionCode -> {
@ -2930,8 +2952,8 @@ object ChatController {
}
is CR.ContactDisabled -> {
if (active(r.user)) {
withChats {
updateContact(rhId, r.contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, r.contact)
}
}
}
@ -3055,8 +3077,8 @@ object ChatController {
}
is CR.ContactPQEnabled ->
if (active(r.user)) {
withChats {
updateContact(rhId, r.contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, r.contact)
}
}
is CR.ChatRespError -> when {
@ -3120,8 +3142,8 @@ object ChatController {
suspend fun leaveGroup(rh: Long?, groupId: Long) {
val groupInfo = apiLeaveGroup(rh, groupId)
if (groupInfo != null) {
withChats {
updateGroup(rh, groupInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rh, groupInfo)
}
}
}
@ -3130,10 +3152,12 @@ object ChatController {
if (activeUser(rh, user)) {
val cInfo = aChatItem.chatInfo
val cItem = aChatItem.chatItem
withChats { upsertChatItem(rh, cInfo, cItem) }
withReportsChatsIfOpen {
if (cItem.isReport) {
upsertChatItem(rh, cInfo, cItem)
withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) }
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
if (cItem.isReport) {
chatModel.secondaryChatsContext.upsertChatItem(rh, cInfo, cItem)
}
}
}
}
@ -3147,15 +3171,16 @@ object ChatController {
return
}
val cInfo = ChatInfo.Group(r.groupInfo)
withChats {
withContext(Dispatchers.Main) {
val chatsCtx = chatModel.chatsContext
r.chatItemIDs.forEach { itemId ->
decreaseGroupReportsCounter(rhId, cInfo.id)
val cItem = chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach
chatsCtx.decreaseGroupReportsCounter(rhId, cInfo.id)
val cItem = chatsCtx.chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach
if (chatModel.chatId.value != null) {
// Stop voice playback only inside a chat, allow to play in a chat list
AudioPlayer.stop(cItem)
}
val isLastChatItem = getChat(cInfo.id)?.chatItems?.lastOrNull()?.id == cItem.id
val isLastChatItem = chatsCtx.getChat(cInfo.id)?.chatItems?.lastOrNull()?.id == cItem.id
if (isLastChatItem && ntfManager.hasNotificationsForChat(cInfo.id)) {
ntfManager.cancelNotificationsForChat(cInfo.id)
ntfManager.displayNotification(
@ -3170,22 +3195,25 @@ object ChatController {
} else {
CIDeleted.Deleted(Clock.System.now())
}
upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted)))
chatsCtx.upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted)))
}
}
withReportsChatsIfOpen {
r.chatItemIDs.forEach { itemId ->
val cItem = chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach
if (chatModel.chatId.value != null) {
// Stop voice playback only inside a chat, allow to play in a chat list
AudioPlayer.stop(cItem)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
val chatsCtx = chatModel.secondaryChatsContext
r.chatItemIDs.forEach { itemId ->
val cItem = chatsCtx.chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach
if (chatModel.chatId.value != null) {
// Stop voice playback only inside a chat, allow to play in a chat list
AudioPlayer.stop(cItem)
}
val deleted = if (r.member_ != null && (cItem.chatDir as CIDirection.GroupRcv?)?.groupMember?.groupMemberId != r.member_.groupMemberId) {
CIDeleted.Moderated(Clock.System.now(), r.member_)
} else {
CIDeleted.Deleted(Clock.System.now())
}
chatsCtx.upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted)))
}
val deleted = if (r.member_ != null && (cItem.chatDir as CIDirection.GroupRcv?)?.groupMember?.groupMemberId != r.member_.groupMemberId) {
CIDeleted.Moderated(Clock.System.now(), r.member_)
} else {
CIDeleted.Deleted(Clock.System.now())
}
upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted)))
}
}
}
@ -3197,8 +3225,14 @@ object ChatController {
if (!activeUser(rh, user)) {
notify()
} else {
val createdChat = withChats { upsertChatItem(rh, cInfo, cItem) }
withReportsChatsIfOpen { if (cItem.content.msgContent is MsgContent.MCReport) { upsertChatItem(rh, cInfo, cItem) } }
val createdChat = withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) }
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
if (cItem.content.msgContent is MsgContent.MCReport) {
chatModel.secondaryChatsContext.upsertChatItem(rh, cInfo, cItem)
}
}
}
if (createdChat) {
notify()
} else if (cItem.content is CIContent.RcvCall && cItem.content.status == CICallStatus.Missed) {
@ -3243,15 +3277,17 @@ object ChatController {
chatModel.users.addAll(users)
chatModel.currentUser.value = user
if (user == null) {
withChats {
chatItems.clearAndNotify()
chats.clear()
popChatCollector.clear()
withContext(Dispatchers.Main) {
chatModel.chatsContext.chatItems.clearAndNotify()
chatModel.chatsContext.chats.clear()
chatModel.chatsContext.popChatCollector.clear()
}
withReportsChatsIfOpen {
chatItems.clearAndNotify()
chats.clear()
popChatCollector.clear()
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.chatItems.clearAndNotify()
chatModel.secondaryChatsContext.chats.clear()
chatModel.secondaryChatsContext.popChatCollector.clear()
}
}
}
val statuses = apiGetNetworkStatuses(rhId)

View file

@ -19,7 +19,6 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.*
@ -37,17 +36,16 @@ 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.helpers.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.common.platform.*
import chat.simplex.common.views.chat.group.ChatTTLSection
import chat.simplex.common.views.chat.group.ProgressIndicator
import chat.simplex.common.views.chatlist.updateChatSettings
import chat.simplex.common.views.database.*
import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
import kotlinx.coroutines.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -126,8 +124,8 @@ fun ChatInfoView(
val cStats = chatModel.controller.apiSwitchContact(chatRh, contact.contactId)
connStats.value = cStats
if (cStats != null) {
withChats {
updateContactConnectionStats(chatRh, contact, cStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnectionStats(chatRh, contact, cStats)
}
}
close.invoke()
@ -140,8 +138,8 @@ fun ChatInfoView(
val cStats = chatModel.controller.apiAbortSwitchContact(chatRh, contact.contactId)
connStats.value = cStats
if (cStats != null) {
withChats {
updateContactConnectionStats(chatRh, contact, cStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnectionStats(chatRh, contact, cStats)
}
}
}
@ -171,8 +169,8 @@ fun ChatInfoView(
verify = { code ->
chatModel.controller.apiVerifyContact(chatRh, ct.contactId, code)?.let { r ->
val (verified, existingCode) = r
withChats {
updateContact(
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(
chatRh,
ct.copy(
activeConn = ct.activeConn?.copy(
@ -200,8 +198,8 @@ suspend fun syncContactConnection(rhId: Long?, contact: Contact, connectionStats
val cStats = chatModel.controller.apiSyncContactRatchet(rhId, contact.contactId, force = force)
connectionStats.value = cStats
if (cStats != null) {
withChats {
updateContactConnectionStats(rhId, contact, cStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnectionStats(rhId, contact, cStats)
}
}
}
@ -475,14 +473,14 @@ fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, chatDe
val chatRh = chat.remoteHostId
val ct = chatModel.controller.apiDeleteContact(chatRh, chatInfo.apiId, chatDeleteMode)
if (ct != null) {
withChats {
withContext(Dispatchers.Main) {
when (chatDeleteMode) {
is ChatDeleteMode.Full ->
removeChat(chatRh, chatInfo.id)
chatModel.chatsContext.removeChat(chatRh, chatInfo.id)
is ChatDeleteMode.Entity ->
updateContact(chatRh, ct)
chatModel.chatsContext.updateContact(chatRh, ct)
is ChatDeleteMode.Messages ->
clearChat(chatRh, ChatInfo.Direct(ct))
chatModel.chatsContext.clearChat(chatRh, ChatInfo.Direct(ct))
}
}
if (chatModel.chatId.value == chatInfo.id) {
@ -1270,11 +1268,11 @@ suspend fun save(applyToMode: DefaultThemeMode?, newTheme: ThemeModeOverride?, c
wallpaperFilesToDelete.forEach(::removeWallpaperFile)
if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) {
withChats {
withContext(Dispatchers.Main) {
if (chat.chatInfo is ChatInfo.Direct) {
updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes)))
chatModel.chatsContext.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)))
chatModel.chatsContext.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes)))
}
}
}
@ -1283,8 +1281,8 @@ 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 {
withChats {
updateContact(chatRh, it)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(chatRh, it)
}
}
}
@ -1386,10 +1384,10 @@ private suspend fun afterSetChatTTL(rhId: Long?, chatInfo: ChatInfo, progressInd
val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, null, pagination) ?: return
if (chat.chatItems.isEmpty()) {
// replacing old chat with the same old chat but without items. Less intrusive way of clearing a preview
withChats {
val oldChat = getChat(chat.id)
withContext(Dispatchers.Main) {
val oldChat = chatModel.chatsContext.getChat(chat.id)
if (oldChat != null) {
replaceChat(oldChat.remoteHostId, oldChat.id, oldChat.copy(chatItems = emptyList()))
chatModel.chatsContext.replaceChat(oldChat.remoteHostId, oldChat.id, oldChat.copy(chatItems = emptyList()))
}
}
}

View file

@ -2,7 +2,6 @@ package chat.simplex.common.views.chat
import androidx.compose.runtime.snapshots.SnapshotStateList
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.platform.chatModel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.StateFlow
@ -48,29 +47,30 @@ suspend fun processLoadedChat(
openAroundItemId: Long?,
visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 }
) {
val chatState = chatModel.chatStateForContent(contentTag)
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
val chatState = chatsCtx.chatState
val (splits, unreadAfterItemId, totalAfter, unreadTotal, unreadAfter, unreadAfterNewestLoaded) = chatState
val oldItems = chatModel.chatItemsForContent(contentTag).value
val oldItems = chatsCtx.chatItems.value
val newItems = SnapshotStateList<ChatItem>()
when (pagination) {
is ChatPagination.Initial -> {
val newSplits = if (chat.chatItems.isNotEmpty() && navInfo.afterTotal > 0) listOf(chat.chatItems.last().id) else emptyList()
if (contentTag == null) {
// update main chats, not content tagged
withChats {
val oldChat = getChat(chat.id)
withContext(Dispatchers.Main) {
val oldChat = chatModel.chatsContext.getChat(chat.id)
if (oldChat == null) {
addChat(chat)
chatModel.chatsContext.addChat(chat)
} else {
updateChatInfo(chat.remoteHostId, chat.chatInfo)
chatModel.chatsContext.updateChatInfo(chat.remoteHostId, chat.chatInfo)
// unreadChat is currently not actual in getChat query (always false)
updateChatStats(chat.remoteHostId, chat.id, chat.chatStats.copy(unreadChat = oldChat.chatStats.unreadChat))
chatModel.chatsContext.updateChatStats(chat.remoteHostId, chat.id, chat.chatStats.copy(unreadChat = oldChat.chatStats.unreadChat))
}
}
}
withChats(contentTag) {
chatItemStatuses.clear()
chatItems.replaceAll(chat.chatItems)
withContext(Dispatchers.Main) {
chatsCtx.chatItemStatuses.clear()
chatsCtx.chatItems.replaceAll(chat.chatItems)
chatModel.chatId.value = chat.id
splits.value = newSplits
if (chat.chatItems.isNotEmpty()) {
@ -93,8 +93,8 @@ suspend fun processLoadedChat(
)
val insertAt = (indexInCurrentItems - (wasSize - newItems.size) + trimmedIds.size).coerceAtLeast(0)
newItems.addAll(insertAt, chat.chatItems)
withChats(contentTag) {
chatItems.replaceAll(newItems)
withContext(Dispatchers.Main) {
chatsCtx.chatItems.replaceAll(newItems)
splits.value = newSplits
chatState.moveUnreadAfterItem(oldUnreadSplitIndex, newUnreadSplitIndex, oldItems)
}
@ -112,8 +112,8 @@ suspend fun processLoadedChat(
val indexToAdd = min(indexInCurrentItems + 1, newItems.size)
val indexToAddIsLast = indexToAdd == newItems.size
newItems.addAll(indexToAdd, chat.chatItems)
withChats(contentTag) {
chatItems.replaceAll(newItems)
withContext(Dispatchers.Main) {
chatsCtx.chatItems.replaceAll(newItems)
splits.value = newSplits
chatState.moveUnreadAfterItem(splits.value.firstOrNull() ?: newItems.last().id, newItems)
// loading clear bottom area, updating number of unread items after the newest loaded item
@ -134,8 +134,8 @@ suspend fun processLoadedChat(
newItems.addAll(itemIndex, chat.chatItems)
newSplits.add(splitIndex, chat.chatItems.last().id)
withChats(contentTag) {
chatItems.replaceAll(newItems)
withContext(Dispatchers.Main) {
chatsCtx.chatItems.replaceAll(newItems)
splits.value = newSplits
unreadAfterItemId.value = chat.chatItems.last().id
totalAfter.value = navInfo.afterTotal
@ -157,8 +157,8 @@ suspend fun processLoadedChat(
val newSplits = removeDuplicatesAndUnusedSplits(newItems, chat, chatState.splits.value)
removeDuplicates(newItems, chat)
newItems.addAll(chat.chatItems)
withChats(contentTag) {
chatItems.replaceAll(newItems)
withContext(Dispatchers.Main) {
chatsCtx.chatItems.replaceAll(newItems)
chatState.splits.value = newSplits
unreadAfterNewestLoaded.value = 0
}

View file

@ -32,8 +32,6 @@ import chat.simplex.common.model.CIDirection.GroupRcv
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel.activeCall
import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.call.*
import chat.simplex.common.views.chat.group.*
@ -114,9 +112,10 @@ fun ChatView(
val chatRh = remoteHostId.value
// We need to have real unreadCount value for displaying it inside top right button
// Having activeChat reloaded on every change in it is inefficient (UI lags)
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
val unreadCount = remember {
derivedStateOf {
chatModel.chatsForContent(contentTag).value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats?.unreadCount ?: 0
chatsCtx.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats?.unreadCount ?: 0
}
}
val clipboard = LocalClipboardManager.current
@ -389,23 +388,25 @@ fun ChatView(
if (deleted != null) {
deletedChatItem = deleted.deletedChatItem.chatItem
toChatItem = deleted.toChatItem?.chatItem
withChats {
withContext(Dispatchers.Main) {
if (toChatItem != null) {
upsertChatItem(chatRh, chatInfo, toChatItem)
chatModel.chatsContext.upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
removeChatItem(chatRh, chatInfo, deletedChatItem)
chatModel.chatsContext.removeChatItem(chatRh, chatInfo, deletedChatItem)
}
val deletedItem = deleted.deletedChatItem.chatItem
if (deletedItem.isActiveReport) {
decreaseGroupReportsCounter(chatRh, chatInfo.id)
chatModel.chatsContext.decreaseGroupReportsCounter(chatRh, chatInfo.id)
}
}
withReportsChatsIfOpen {
if (deletedChatItem.isReport) {
if (toChatItem != null) {
upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
removeChatItem(chatRh, chatInfo, deletedChatItem)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
if (deletedChatItem.isReport) {
if (toChatItem != null) {
chatModel.secondaryChatsContext.upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
chatModel.secondaryChatsContext.removeChatItem(chatRh, chatInfo, deletedChatItem)
}
}
}
}
@ -463,8 +464,8 @@ fun ChatView(
if (r != null) {
val contactStats = r.first
if (contactStats != null)
withChats {
updateContactConnectionStats(chatRh, contact, contactStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnectionStats(chatRh, contact, contactStats)
}
}
}
@ -475,8 +476,8 @@ fun ChatView(
if (r != null) {
val memStats = r.second
if (memStats != null) {
withChats {
updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
}
}
}
@ -486,8 +487,8 @@ fun ChatView(
withBGApi {
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
if (cStats != null) {
withChats {
updateContactConnectionStats(chatRh, contact, cStats)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnectionStats(chatRh, contact, cStats)
}
}
}
@ -496,8 +497,8 @@ fun ChatView(
withBGApi {
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
if (r != null) {
withChats {
updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
}
}
}
@ -519,12 +520,14 @@ fun ChatView(
reaction = reaction
)
if (updatedCI != null) {
withChats {
updateChatItem(cInfo, updatedCI)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatItem(cInfo, updatedCI)
}
withReportsChatsIfOpen {
if (cItem.isReport) {
updateChatItem(cInfo, updatedCI)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
if (cItem.isReport) {
chatModel.secondaryChatsContext.updateChatItem(cInfo, updatedCI)
}
}
}
}
@ -544,7 +547,7 @@ fun ChatView(
groupMembersJob.cancel()
groupMembersJob = scope.launch(Dispatchers.Default) {
var initialCiInfo = loadChatItemInfo() ?: return@launch
if (!ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) {
if (!ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
ModalManager.end.closeModals()
}
ModalManager.end.showModalCloseable(endButtons = {
@ -578,11 +581,8 @@ fun ChatView(
openGroupLink = { groupInfo -> openGroupLink(view = view, groupInfo = groupInfo, rhId = chatRh, close = { ModalManager.end.closeModals() }) },
markItemsRead = { itemsIds ->
withBGApi {
withChats {
// It's important to call it on Main thread. Otherwise, composable crash occurs from time-to-time without useful stacktrace
withContext(Dispatchers.Main) {
markChatItemsRead(chatRh, chatInfo.id, itemsIds)
}
withContext(Dispatchers.Main) {
chatModel.chatsContext.markChatItemsRead(chatRh, chatInfo.id, itemsIds)
ntfManager.cancelNotificationsForChat(chatInfo.id)
chatModel.controller.apiChatItemsRead(
chatRh,
@ -591,18 +591,17 @@ fun ChatView(
itemsIds
)
}
withReportsChatsIfOpen {
markChatItemsRead(chatRh, chatInfo.id, itemsIds)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.markChatItemsRead(chatRh, chatInfo.id, itemsIds)
}
}
}
},
markChatRead = {
withBGApi {
withChats {
// It's important to call it on Main thread. Otherwise, composable crash occurs from time-to-time without useful stacktrace
withContext(Dispatchers.Main) {
markChatItemsRead(chatRh, chatInfo.id)
}
withContext(Dispatchers.Main) {
chatModel.chatsContext.markChatItemsRead(chatRh, chatInfo.id)
ntfManager.cancelNotificationsForChat(chatInfo.id)
chatModel.controller.apiChatRead(
chatRh,
@ -610,8 +609,10 @@ fun ChatView(
chatInfo.apiId
)
}
withReportsChatsIfOpen {
markChatItemsRead(chatRh, chatInfo.id)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.markChatItemsRead(chatRh, chatInfo.id)
}
}
}
},
@ -636,8 +637,8 @@ fun ChatView(
LaunchedEffect(chatInfo.id) {
onComposed(chatInfo.id)
ModalManager.end.closeModals()
withChats {
chatItems.clearAndNotify()
withContext(Dispatchers.Main) {
chatModel.chatsContext.chatItems.clearAndNotify()
}
}
}
@ -649,8 +650,8 @@ fun ChatView(
LaunchedEffect(chatInfo.id) {
onComposed(chatInfo.id)
ModalManager.end.closeModals()
withChats {
chatItems.clearAndNotify()
withContext(Dispatchers.Main) {
chatModel.chatsContext.chatItems.clearAndNotify()
}
}
}
@ -1206,12 +1207,13 @@ fun BoxScope.ChatItemsList(
val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf<Long>()) }
val contentTag = LocalContentTag.current
// not using reversedChatItems inside to prevent possible derivedState bug in Compose when one derived state access can cause crash asking another derived state
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
val mergedItems = remember {
derivedStateOf {
MergedItems.create(chatModel.chatItemsForContent(contentTag).value.asReversed(), unreadCount, revealedItems.value, chatModel.chatStateForContent(contentTag))
MergedItems.create(chatsCtx.chatItems.value.asReversed(), unreadCount, revealedItems.value, chatsCtx.chatState)
}
}
val reversedChatItems = remember { derivedStateOf { chatModel.chatItemsForContent(contentTag).value.asReversed() } }
val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } }
val reportsCount = reportsCount(chatInfo.id)
val topPaddingToContent = topPaddingToContent(chatView = contentTag == null, contentTag == null && reportsCount > 0)
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() })
@ -1297,11 +1299,11 @@ fun BoxScope.ChatItemsList(
DisposableEffectOnGone(
always = {
chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(contentTag)), contentTag)
chatsCtx.chatItemsChangesListener = recalculateChatStatePositions(chatsCtx.chatState)
},
whenGone = {
VideoPlayerHolder.releaseAll()
chatModel.setChatItemsChangeListenerForContent(recalculateChatStatePositions(chatModel.chatStateForContent(contentTag)), contentTag)
chatsCtx.chatItemsChangesListener = recalculateChatStatePositions(chatsCtx.chatState)
}
)
@ -1648,8 +1650,9 @@ private suspend fun loadLastItems(chatId: State<ChatId>, contentTag: MsgContentT
}
private fun lastItemsLoaded(contentTag: MsgContentTag?): Boolean {
val chatState = chatModel.chatStateForContent(contentTag)
return chatState.splits.value.isEmpty() || chatState.splits.value.firstOrNull() != chatModel.chatItemsForContent(contentTag).value.lastOrNull()?.id
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
val chatState = chatsCtx.chatState
return chatState.splits.value.isEmpty() || chatState.splits.value.firstOrNull() != chatsCtx.chatItems.value.lastOrNull()?.id
}
// TODO: in extra rare case when after loading last items only 1 item is loaded, the view will jump like when receiving new message
@ -1740,7 +1743,8 @@ fun BoxScope.FloatingButtons(
fun scrollToTopUnread() {
scope.launch {
tryBlockAndSetLoadingMore(loadingMoreItems) {
if (chatModel.chatStateForContent(contentTag).splits.value.isNotEmpty()) {
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
if (chatsCtx.chatState.splits.value.isNotEmpty()) {
val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT)
val oldSize = reversedChatItems.value.size
loadMessages(chatInfo.value.id, pagination) {
@ -2129,7 +2133,7 @@ private fun SaveReportsStateOnDispose(listState: State<LazyListState>) {
val contentTag = LocalContentTag.current
DisposableEffect(Unit) {
onDispose {
reportsListState = if (contentTag == MsgContentTag.Report && ModalManager.end.hasModalOpen(ModalViewId.GROUP_REPORTS)) listState.value else null
reportsListState = if (contentTag == MsgContentTag.Report && ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) listState.value else null
}
}
}
@ -2240,8 +2244,10 @@ fun reportsCount(staleChatId: String?): Int {
}
}
private fun reversedChatItemsStatic(contentTag: MsgContentTag?): List<ChatItem> =
chatModel.chatItemsForContent(contentTag).value.asReversed()
private fun reversedChatItemsStatic(contentTag: MsgContentTag?): List<ChatItem> {
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
return chatsCtx.chatItems.value.asReversed()
}
private fun oldestPartiallyVisibleListItemInListStateOrNull(topPaddingToContentPx: State<Int>, mergedItems: State<MergedItems>, listState: State<LazyListState>): ListItem? {
val lastFullyVisibleOffset = listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value
@ -2326,11 +2332,13 @@ private fun findQuotedItemFromItem(
scope.launch(Dispatchers.Default) {
val item = apiLoadSingleMessage(rhId.value, chatInfo.value.chatType, chatInfo.value.apiId, itemId, contentTag)
if (item != null) {
withChats {
updateChatItem(chatInfo.value, item)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatItem(chatInfo.value, item)
}
withReportsChatsIfOpen {
updateChatItem(chatInfo.value, item)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.updateChatItem(chatInfo.value, item)
}
}
if (item.quotedItem?.itemId != null) {
scrollToItem(item.quotedItem.itemId)
@ -2510,28 +2518,30 @@ private fun deleteMessages(chatRh: Long?, chatInfo: ChatInfo, itemIds: List<Long
)
}
if (deleted != null) {
withChats {
withContext(Dispatchers.Main) {
for (di in deleted) {
val toChatItem = di.toChatItem?.chatItem
if (toChatItem != null) {
upsertChatItem(chatRh, chatInfo, toChatItem)
chatModel.chatsContext.upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
chatModel.chatsContext.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
}
val deletedItem = di.deletedChatItem.chatItem
if (deletedItem.isActiveReport) {
decreaseGroupReportsCounter(chatRh, chatInfo.id)
chatModel.chatsContext.decreaseGroupReportsCounter(chatRh, chatInfo.id)
}
}
}
withReportsChatsIfOpen {
for (di in deleted) {
if (di.deletedChatItem.chatItem.isReport) {
val toChatItem = di.toChatItem?.chatItem
if (toChatItem != null) {
upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
for (di in deleted) {
if (di.deletedChatItem.chatItem.isReport) {
val toChatItem = di.toChatItem?.chatItem
if (toChatItem != null) {
chatModel.secondaryChatsContext.upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
chatModel.secondaryChatsContext.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
}
}
}
}
@ -2552,28 +2562,30 @@ private fun archiveReports(chatRh: Long?, chatInfo: ChatInfo, itemIds: List<Long
mode = if (forAll) CIDeleteMode.cidmBroadcast else CIDeleteMode.cidmInternalMark
)
if (deleted != null) {
withChats {
withContext(Dispatchers.Main) {
for (di in deleted) {
val toChatItem = di.toChatItem?.chatItem
if (toChatItem != null) {
upsertChatItem(chatRh, chatInfo, toChatItem)
chatModel.chatsContext.upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
chatModel.chatsContext.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
}
val deletedItem = di.deletedChatItem.chatItem
if (deletedItem.isActiveReport) {
decreaseGroupReportsCounter(chatRh, chatInfo.id)
chatModel.chatsContext.decreaseGroupReportsCounter(chatRh, chatInfo.id)
}
}
}
withReportsChatsIfOpen {
for (di in deleted) {
if (di.deletedChatItem.chatItem.isReport) {
val toChatItem = di.toChatItem?.chatItem
if (toChatItem != null) {
upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
for (di in deleted) {
if (di.deletedChatItem.chatItem.isReport) {
val toChatItem = di.toChatItem?.chatItem
if (toChatItem != null) {
chatModel.secondaryChatsContext.upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
chatModel.secondaryChatsContext.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
}
}
}
}
@ -2607,9 +2619,9 @@ private fun markUnreadChatAsRead(chatId: String) {
false
)
if (success) {
withChats {
replaceChat(chatRh, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
markChatTagRead(chat)
withContext(Dispatchers.Main) {
chatModel.chatsContext.replaceChat(chatRh, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
chatModel.chatsContext.markChatTagRead(chat)
}
}
}
@ -2791,7 +2803,7 @@ private fun ViewConfiguration.bigTouchSlop(slop: Float = 50f) = object: ViewConf
private fun forwardContent(chatItemsIds: List<Long>, chatInfo: ChatInfo) {
chatModel.chatId.value = null
chatModel.sharedContent.value = SharedContent.Forward(
chatModel.chatItemsForContent(null).value.filter { chatItemsIds.contains(it.id) },
chatModel.chatsContext.chatItems.value.filter { chatItemsIds.contains(it.id) },
chatInfo
)
}

View file

@ -25,7 +25,6 @@ import androidx.compose.ui.util.*
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.*
@ -473,8 +472,8 @@ fun ComposeView(
)
if (!chatItems.isNullOrEmpty()) {
chatItems.forEach { aChatItem ->
withChats {
addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem)
withContext(Dispatchers.Main) {
chatModel.chatsContext.addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem)
}
}
return chatItems.first().chatItem
@ -505,9 +504,9 @@ fun ComposeView(
ttl = ttl
)
withChats {
withContext(Dispatchers.Main) {
chatItems?.forEach { chatItem ->
addChatItem(rhId, chat.chatInfo, chatItem)
chatModel.chatsContext.addChatItem(rhId, chat.chatInfo, chatItem)
}
}
@ -567,9 +566,9 @@ fun ComposeView(
suspend fun sendReport(reportReason: ReportReason, chatItemId: Long): List<ChatItem>? {
val cItems = chatModel.controller.apiReportMessage(chat.remoteHostId, chat.chatInfo.apiId, chatItemId, reportReason, msgText)
if (cItems != null) {
withChats {
withContext(Dispatchers.Main) {
cItems.forEach { chatItem ->
addChatItem(chat.remoteHostId, chat.chatInfo, chatItem.chatItem)
chatModel.chatsContext.addChatItem(chat.remoteHostId, chat.chatInfo, chatItem.chatItem)
}
}
}
@ -581,8 +580,8 @@ fun ComposeView(
val mc = checkLinkPreview()
val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc)
if (contact != null) {
withChats {
updateContact(chat.remoteHostId, contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(chat.remoteHostId, contact)
}
}
}
@ -599,8 +598,10 @@ fun ComposeView(
updatedMessage = UpdatedMessage(updateMsgContent(oldMsgContent), cs.memberMentions),
live = live
)
if (updatedItem != null) withChats {
upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem)
if (updatedItem != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem)
}
}
return updatedItem?.chatItem
}
@ -890,7 +891,7 @@ fun ComposeView(
fun editPrevMessage() {
if (composeState.value.contextItem != ComposeContextItem.NoContextItem || composeState.value.preview != ComposePreview.NoPreview) return
val lastEditable = chatModel.chatItemsForContent(null).value.findLast { it.meta.editable }
val lastEditable = chatModel.chatsContext.chatItems.value.findLast { it.meta.editable }
if (lastEditable != null) {
composeState.value = ComposeState(editingItem = lastEditable, useLinkPreviews = useLinkPreviews)
}

View file

@ -12,16 +12,16 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import dev.icerock.moko.resources.compose.stringResource
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.common.platform.chatModel
import chat.simplex.res.MR
import kotlinx.coroutines.*
@Composable
fun ContactPreferencesView(
@ -41,8 +41,8 @@ fun ContactPreferencesView(
val prefs = contactFeaturesAllowedToPrefs(featuresAllowed)
val toContact = m.controller.apiSetContactPrefs(rhId, ct.contactId, prefs)
if (toContact != null) {
withChats {
updateContact(rhId, toContact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, toContact)
currentFeaturesAllowed = featuresAllowed
}
}

View file

@ -15,6 +15,7 @@ import androidx.compose.ui.unit.dp
import chat.simplex.common.model.*
import chat.simplex.common.platform.BackHandler
import chat.simplex.common.platform.chatModel
import chat.simplex.common.views.chat.group.LocalContentTag
import chat.simplex.common.views.helpers.*
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.res.MR
@ -121,7 +122,8 @@ fun SelectedItemsButtonsToolbar(
}
Divider(Modifier.align(Alignment.TopStart))
}
val chatItems = remember { derivedStateOf { chatModel.chatItemsForContent(contentTag).value } }
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
val chatItems = remember { derivedStateOf { chatsCtx.chatItems.value } }
LaunchedEffect(chatInfo, chatItems.value, selectedChatItems.value) {
recheckItems(chatInfo, chatItems.value, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canArchiveReports, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited)
}

View file

@ -25,8 +25,6 @@ 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.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.ChatInfoToolbarTitle
import chat.simplex.common.views.helpers.*
@ -35,6 +33,7 @@ import chat.simplex.common.model.GroupInfo
import chat.simplex.common.platform.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.StringResource
import kotlinx.coroutines.*
@Composable
fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolean = false, chatModel: ChatModel, close: () -> Unit) {
@ -62,11 +61,13 @@ 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) {
withChats {
upsertGroupMember(rhId, groupInfo, member)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, member)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, groupInfo, member)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, member)
}
}
} else {
break

View file

@ -32,8 +32,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
@ -182,8 +180,8 @@ fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, cl
withBGApi {
val r = chatModel.controller.apiDeleteChat(chat.remoteHostId, chatInfo.chatType, chatInfo.apiId)
if (r) {
withChats {
removeChat(chat.remoteHostId, chatInfo.id)
withContext(Dispatchers.Main) {
chatModel.chatsContext.removeChat(chat.remoteHostId, chatInfo.id)
if (chatModel.chatId.value == chatInfo.id) {
chatModel.chatId.value = null
ModalManager.end.closeModals()
@ -957,8 +955,8 @@ private fun SearchRowView(
private fun setGroupAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi {
val chatRh = chat.remoteHostId
chatModel.controller.apiSetGroupAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let {
withChats {
updateGroup(chatRh, it)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(chatRh, it)
}
}
}
@ -967,14 +965,16 @@ fun removeMembers(rhId: Long?, groupInfo: GroupInfo, memberIds: List<Long>, onSu
withBGApi {
val updatedMembers = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds)
if (updatedMembers != null) {
withChats {
withContext(Dispatchers.Main) {
updatedMembers.forEach { updatedMember ->
upsertGroupMember(rhId, groupInfo, updatedMember)
chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, updatedMember)
}
}
withReportsChatsIfOpen {
updatedMembers.forEach { updatedMember ->
upsertGroupMember(rhId, groupInfo, updatedMember)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
updatedMembers.forEach { updatedMember ->
chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, updatedMember)
}
}
}
onSuccess()

View file

@ -27,8 +27,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.*
import chat.simplex.common.views.helpers.*
@ -40,6 +38,7 @@ import chat.simplex.common.views.chatlist.openLoadedChat
import chat.simplex.res.MR
import dev.icerock.moko.resources.StringResource
import kotlinx.datetime.Clock
import kotlinx.coroutines.*
@Composable
fun GroupMemberInfoView(
@ -63,11 +62,13 @@ fun GroupMemberInfoView(
val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = false)
if (r != null) {
connStats.value = r.second
withChats {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
withReportsChatsIfOpen {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
}
close.invoke()
}
@ -100,8 +101,8 @@ 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())
withChats {
addChat(memberChat)
withContext(Dispatchers.Main) {
chatModel.chatsContext.addChat(memberChat)
}
openLoadedChat(memberChat)
closeAll()
@ -149,11 +150,13 @@ fun GroupMemberInfoView(
val r = chatModel.controller.apiSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId)
if (r != null) {
connStats.value = r.second
withChats {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
withReportsChatsIfOpen {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
}
close.invoke()
}
@ -166,11 +169,13 @@ fun GroupMemberInfoView(
val r = chatModel.controller.apiAbortSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId)
if (r != null) {
connStats.value = r.second
withChats {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
withReportsChatsIfOpen {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
}
close.invoke()
}
@ -186,11 +191,13 @@ fun GroupMemberInfoView(
val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = true)
if (r != null) {
connStats.value = r.second
withChats {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
withReportsChatsIfOpen {
updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second)
}
}
close.invoke()
}
@ -212,11 +219,13 @@ fun GroupMemberInfoView(
connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null
)
)
withChats {
upsertGroupMember(rhId, groupInfo, copy)
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, copy)
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, groupInfo, copy)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, copy)
}
}
r
}
@ -247,14 +256,16 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c
withBGApi {
val removedMembers = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId))
if (removedMembers != null) {
withChats {
withContext(Dispatchers.Main) {
removedMembers.forEach { removedMember ->
upsertGroupMember(rhId, groupInfo, removedMember)
chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, removedMember)
}
}
withReportsChatsIfOpen {
removedMembers.forEach { removedMember ->
upsertGroupMember(rhId, groupInfo, removedMember)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
removedMembers.forEach { removedMember ->
chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, removedMember)
}
}
}
}
@ -697,14 +708,16 @@ fun updateMembersRole(newRole: GroupMemberRole, rhId: Long?, groupInfo: GroupInf
withBGApi {
kotlin.runCatching {
val members = chatModel.controller.apiMembersRole(rhId, groupInfo.groupId, memberIds, newRole)
withChats {
withContext(Dispatchers.Main) {
members.forEach { member ->
upsertGroupMember(rhId, groupInfo, member)
chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, member)
}
}
withReportsChatsIfOpen {
members.forEach { member ->
upsertGroupMember(rhId, groupInfo, member)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
members.forEach { member ->
chatModel.secondaryChatsContext.upsertGroupMember(rhId, groupInfo, member)
}
}
}
onSuccess()
@ -798,11 +811,13 @@ fun updateMemberSettings(rhId: Long?, gInfo: GroupInfo, member: GroupMember, mem
withBGApi {
val success = ChatController.apiSetMemberSettings(rhId, gInfo.groupId, member.groupMemberId, memberSettings)
if (success) {
withChats {
upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings))
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings))
}
withReportsChatsIfOpen {
upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings))
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings))
}
}
}
}
@ -857,14 +872,16 @@ fun unblockForAllAlert(rhId: Long?, gInfo: GroupInfo, memberIds: List<Long>, onS
fun blockMemberForAll(rhId: Long?, gInfo: GroupInfo, memberIds: List<Long>, blocked: Boolean, onSuccess: () -> Unit = {}) {
withBGApi {
val updatedMembers = ChatController.apiBlockMembersForAll(rhId, gInfo.groupId, memberIds, blocked)
withChats {
withContext(Dispatchers.Main) {
updatedMembers.forEach { updatedMember ->
upsertGroupMember(rhId, gInfo, updatedMember)
chatModel.chatsContext.upsertGroupMember(rhId, gInfo, updatedMember)
}
}
withReportsChatsIfOpen {
updatedMembers.forEach { updatedMember ->
upsertGroupMember(rhId, gInfo, updatedMember)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
updatedMembers.forEach { updatedMember ->
chatModel.secondaryChatsContext.upsertGroupMember(rhId, gInfo, updatedMember)
}
}
}
onSuccess()

View file

@ -15,9 +15,10 @@ 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.common.platform.chatModel
import chat.simplex.res.MR
import kotlinx.coroutines.*
private val featureRoles: List<Pair<GroupMemberRole?, String>> = listOf(
null to generalGetString(MR.strings.feature_roles_all_members),
@ -42,12 +43,12 @@ 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) {
withChats {
updateGroup(rhId, g)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, g)
currentPreferences = preferences
}
withChats {
updateGroup(rhId, g)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, g)
}
}
afterSave()

View file

@ -17,8 +17,6 @@ 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.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.*
@ -27,8 +25,7 @@ import chat.simplex.common.views.onboarding.ReadableText
import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.painterResource
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import java.net.URI
@Composable
@ -40,8 +37,8 @@ fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl
withBGApi {
val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p)
if (gInfo != null) {
withChats {
updateGroup(rhId, gInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, gInfo)
}
close.invoke()
}

View file

@ -72,7 +72,7 @@ private fun ItemsReload(contentTag: MsgContentTag?) {
suspend fun showGroupReportsView(staleChatId: State<String?>, scrollToItemId: MutableState<Long?>, chatInfo: ChatInfo) {
openChat(chatModel.remoteHostId(), chatInfo, MsgContentTag.Report)
ModalManager.end.showCustomModal(true, id = ModalViewId.GROUP_REPORTS) { close ->
ModalManager.end.showCustomModal(true, id = ModalViewId.SECONDARY_CHAT) { close ->
ModalView({}, showAppBar = false) {
val chatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chatModel.chatId.value }?.chatInfo } }.value
if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate) {

View file

@ -26,13 +26,10 @@ 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.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.model.GroupInfo
import chat.simplex.common.platform.ColumnWithScrollBar
import chat.simplex.common.platform.chatJsonLength
import chat.simplex.common.platform.*
import chat.simplex.res.MR
import kotlinx.coroutines.delay
import kotlinx.coroutines.*
private const val maxByteCount = 1200
@ -51,8 +48,8 @@ fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: ()
val res = m.controller.apiUpdateGroup(rhId, gInfo.groupId, groupProfileUpdated)
if (res != null) {
gInfo = res
withChats {
updateGroup(rhId, res)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, res)
}
welcomeText.value = welcome ?: ""
}

View file

@ -12,6 +12,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.getChatItemIndexOrNull
import chat.simplex.common.platform.chatModel
import chat.simplex.common.platform.onRightClick
import chat.simplex.common.views.chat.group.LocalContentTag
@ -76,7 +77,8 @@ private fun mergedFeatures(chatItem: ChatItem, chatInfo: ChatInfo): List<Feature
val m = ChatModel
val fs: ArrayList<FeatureInfo> = arrayListOf()
val icons: MutableSet<PainterBox> = mutableSetOf()
val reversedChatItems = m.chatItemsForContent(LocalContentTag.current).value.asReversed()
val chatsCtx = if (LocalContentTag.current == null) m.chatsContext else m.secondaryChatsContext
val reversedChatItems = chatsCtx.chatItems.value.asReversed()
var i = getChatItemIndexOrNull(chatItem, reversedChatItems)
if (i != null) {
while (i < reversedChatItems.size) {

View file

@ -10,7 +10,6 @@ import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.*
import androidx.compose.ui.graphics.*
@ -632,7 +631,8 @@ fun ChatItemView(
}
@Composable fun EventItemView() {
val reversedChatItems = chatModel.chatItemsForContent(LocalContentTag.current).value.asReversed()
val chatsCtx = if (LocalContentTag.current == null) chatModel.chatsContext else chatModel.secondaryChatsContext
val reversedChatItems = chatsCtx.chatItems.value.asReversed()
CIEventView(eventItemViewText(reversedChatItems))
}
@ -839,13 +839,14 @@ fun DeleteItemAction(
buttonText: String = stringResource(MR.strings.delete_verb),
) {
val contentTag = LocalContentTag.current
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
ItemAction(
buttonText,
painterResource(MR.images.ic_delete),
onClick = {
showMenu.value = false
if (!revealed.value) {
val reversedChatItems = chatModel.chatItemsForContent(contentTag).value.asReversed()
val reversedChatItems = chatsCtx.chatItems.value.asReversed()
val currIndex = chatModel.getChatItemIndexOrNull(cItem, reversedChatItems)
val ciCategory = cItem.mergeCategory
if (currIndex != null && ciCategory != null) {
@ -1314,7 +1315,7 @@ fun shapeStyle(chatItem: ChatItem? = null, tailEnabled: Boolean, tailVisible: Bo
}
private fun closeReportsIfNeeded() {
if (appPlatform.isAndroid && ModalManager.end.isLastModalOpen(ModalViewId.GROUP_REPORTS)) {
if (appPlatform.isAndroid && ModalManager.end.isLastModalOpen(ModalViewId.SECONDARY_CHAT)) {
ModalManager.end.closeModals()
}
}

View file

@ -44,7 +44,8 @@ fun MarkedDeletedItemView(ci: ChatItem, chatInfo: ChatInfo, timedMessagesTTL: In
@Composable
private fun MergedMarkedDeletedText(chatItem: ChatItem, chatInfo: ChatInfo, revealed: State<Boolean>) {
val reversedChatItems = chatModel.chatItemsForContent(LocalContentTag.current).value.asReversed()
val chatsCtx = if (LocalContentTag.current == null) chatModel.chatsContext else chatModel.secondaryChatsContext
val reversedChatItems = chatsCtx.chatItems.value.asReversed()
var i = getChatItemIndexOrNull(chatItem, reversedChatItems)
val ciCategory = chatItem.mergeCategory
val text = if (!revealed.value && ciCategory != null && i != null) {

View file

@ -20,8 +20,6 @@ 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.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.*
@ -233,17 +231,19 @@ suspend fun openChat(
)
suspend fun openLoadedChat(chat: Chat, contentTag: MsgContentTag? = null) {
withChats(contentTag) {
chatItemStatuses.clear()
chatItems.replaceAll(chat.chatItems)
withContext(Dispatchers.Main) {
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
chatsCtx.chatItemStatuses.clear()
chatsCtx.chatItems.replaceAll(chat.chatItems)
chatModel.chatId.value = chat.chatInfo.id
chatModel.chatStateForContent(contentTag).clear()
chatsCtx.chatState.clear()
}
}
suspend fun apiFindMessages(ch: Chat, search: String, contentTag: MsgContentTag?) {
withChats(contentTag) {
chatItems.clearAndNotify()
withContext(Dispatchers.Main) {
val chatsCtx = if (contentTag == null) chatModel.chatsContext else chatModel.secondaryChatsContext
chatsCtx.chatItems.clearAndNotify()
}
apiLoadMessages(ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, contentTag, pagination = if (search.isNotEmpty()) ChatPagination.Last(ChatPagination.INITIAL_COUNT) else ChatPagination.Initial(ChatPagination.INITIAL_COUNT), search = search)
}
@ -604,11 +604,13 @@ fun markChatRead(c: Chat) {
var chat = c
withApi {
if (chat.chatStats.unreadCount > 0) {
withChats {
markChatItemsRead(chat.remoteHostId, chat.chatInfo.id)
withContext(Dispatchers.Main) {
chatModel.chatsContext.markChatItemsRead(chat.remoteHostId, chat.chatInfo.id)
}
withReportsChatsIfOpen {
markChatItemsRead(chat.remoteHostId, chat.chatInfo.id)
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.markChatItemsRead(chat.remoteHostId, chat.chatInfo.id)
}
}
chatModel.controller.apiChatRead(
chat.remoteHostId,
@ -625,9 +627,9 @@ fun markChatRead(c: Chat) {
false
)
if (success) {
withChats {
replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
markChatTagRead(chat)
withContext(Dispatchers.Main) {
chatModel.chatsContext.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)))
chatModel.chatsContext.markChatTagRead(chat)
}
}
}
@ -647,9 +649,9 @@ fun markChatUnread(chat: Chat, chatModel: ChatModel) {
true
)
if (success) {
withChats {
replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true)))
updateChatTagReadNoContentTag(chat, wasUnread)
withContext(Dispatchers.Main) {
chatModel.chatsContext.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true)))
chatModel.chatsContext.updateChatTagReadNoContentTag(chat, wasUnread)
}
}
}
@ -690,8 +692,8 @@ 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())
withChats {
replaceChat(rhId, contactRequest.id, chat)
withContext(Dispatchers.Main) {
chatModel.chatsContext.replaceChat(rhId, contactRequest.id, chat)
}
chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected())
close?.invoke(chat)
@ -702,8 +704,8 @@ 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)
withChats {
removeChat(rhId, contactRequest.id)
withContext(Dispatchers.Main) {
chatModel.chatsContext.removeChat(rhId, contactRequest.id)
}
}
}
@ -720,8 +722,8 @@ fun deleteContactConnectionAlert(rhId: Long?, connection: PendingContactConnecti
withBGApi {
AlertManager.shared.hideAlert()
if (chatModel.controller.apiDeleteChat(rhId, ChatType.ContactConnection, connection.apiId)) {
withChats {
removeChat(rhId, connection.id)
withContext(Dispatchers.Main) {
chatModel.chatsContext.removeChat(rhId, connection.id)
}
onSuccess()
}
@ -741,8 +743,8 @@ fun pendingContactAlertDialog(rhId: Long?, chatInfo: ChatInfo, chatModel: ChatMo
withBGApi {
val r = chatModel.controller.apiDeleteChat(rhId, chatInfo.chatType, chatInfo.apiId)
if (r) {
withChats {
removeChat(rhId, chatInfo.id)
withContext(Dispatchers.Main) {
chatModel.chatsContext.removeChat(rhId, chatInfo.id)
}
if (chatModel.chatId.value == chatInfo.id) {
chatModel.chatId.value = null
@ -805,8 +807,8 @@ 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) {
withChats {
updateContact(rhId, contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, contact)
}
AlertManager.privacySensitive.showAlertMsg(
title = generalGetString(MR.strings.connection_request_sent),
@ -848,8 +850,8 @@ fun deleteGroup(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) {
withBGApi {
val r = chatModel.controller.apiDeleteChat(rhId, ChatType.Group, groupInfo.apiId)
if (r) {
withChats {
removeChat(rhId, groupInfo.id)
withContext(Dispatchers.Main) {
chatModel.chatsContext.removeChat(rhId, groupInfo.id)
}
if (chatModel.chatId.value == groupInfo.id) {
chatModel.chatId.value = null
@ -903,16 +905,16 @@ fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: Ch
val wasUnread = chat?.unreadTag ?: false
val wasFavorite = chatInfo.chatSettings?.favorite ?: false
chatModel.updateChatFavorite(favorite = chatSettings.favorite, wasFavorite)
withChats {
updateChatInfo(remoteHostId, newChatInfo)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatInfo(remoteHostId, newChatInfo)
}
if (chatSettings.enableNtfs == MsgFilter.None) {
ntfManager.cancelNotificationsForChat(chatInfo.id)
}
val updatedChat = chatModel.getChat(chatInfo.id)
if (updatedChat != null) {
withChats {
updateChatTagReadNoContentTag(updatedChat, wasUnread)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateChatTagReadNoContentTag(updatedChat, wasUnread)
}
}
val current = currentState?.value

View file

@ -27,7 +27,6 @@ import androidx.compose.ui.unit.*
import chat.simplex.common.AppLock
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatController.setConditionsNotified
import chat.simplex.common.model.ChatController.stopRemoteHostAndReloadHosts
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
@ -38,8 +37,6 @@ import chat.simplex.common.views.chat.topPaddingToContent
import chat.simplex.common.views.newchat.*
import chat.simplex.common.views.onboarding.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.common.views.usersettings.networkAndServers.ConditionsLinkButton
import chat.simplex.common.views.usersettings.networkAndServers.UsageConditionsView
import chat.simplex.res.MR
import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.StringResource
@ -139,7 +136,7 @@ fun ChatListView(chatModel: ChatModel, userPickerState: MutableStateFlow<Animate
if (appPlatform.isDesktop) {
KeyChangeEffect(chatModel.chatId.value) {
if (chatModel.chatId.value != null && !ModalManager.end.isLastModalOpen(ModalViewId.GROUP_REPORTS)) {
if (chatModel.chatId.value != null && !ModalManager.end.isLastModalOpen(ModalViewId.SECONDARY_CHAT)) {
ModalManager.end.closeModalsExceptFirst()
}
AudioPlayer.stop()

View file

@ -32,8 +32,6 @@ import chat.simplex.common.model.ChatController.apiDeleteChatTag
import chat.simplex.common.model.ChatController.apiSetChatTags
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel.clearActiveChatFilterIfNeeded
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.item.ItemAction
@ -43,6 +41,7 @@ import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.*
@Composable
fun TagListView(rhId: Long?, chat: Chat? = null, close: () -> Unit, reorderMode: Boolean) {
@ -417,15 +416,15 @@ private fun setTag(rhId: Long?, tagId: Long?, chat: Chat, close: () -> Unit) {
when (val cInfo = chat.chatInfo) {
is ChatInfo.Direct -> {
val contact = cInfo.contact.copy(chatTags = result.second)
withChats {
updateContact(rhId, contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, contact)
}
}
is ChatInfo.Group -> {
val group = cInfo.groupInfo.copy(chatTags = result.second)
withChats {
updateGroup(rhId, group)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, group)
}
}
@ -453,14 +452,14 @@ private fun deleteTag(rhId: Long?, tag: ChatTag, saving: MutableState<Boolean>)
when (val cInfo = c.chatInfo) {
is ChatInfo.Direct -> {
val contact = cInfo.contact.copy(chatTags = cInfo.contact.chatTags.filter { it != tagId })
withChats {
updateContact(rhId, contact)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContact(rhId, contact)
}
}
is ChatInfo.Group -> {
val group = cInfo.groupInfo.copy(chatTags = cInfo.groupInfo.chatTags.filter { it != tagId })
withChats {
updateGroup(rhId, group)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, group)
}
}
else -> {}

View file

@ -5,7 +5,6 @@ import androidx.compose.ui.graphics.Color
import dev.icerock.moko.resources.compose.painterResource
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.chat.*
import chat.simplex.common.views.chat.item.ItemAction

View file

@ -21,8 +21,6 @@ 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.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
@ -36,6 +34,7 @@ import java.nio.file.StandardCopyOption
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
import kotlinx.coroutines.*
@Composable
fun DatabaseView() {
@ -538,15 +537,17 @@ 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
withLongRunningApi {
withChats {
chatItems.clearAndNotify()
chats.clear()
popChatCollector.clear()
withContext(Dispatchers.Main) {
chatModel.chatsContext.chatItems.clearAndNotify()
chatModel.chatsContext.chats.clear()
chatModel.chatsContext.popChatCollector.clear()
}
withReportsChatsIfOpen {
chatItems.clearAndNotify()
chats.clear()
popChatCollector.clear()
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.chatItems.clearAndNotify()
chatModel.secondaryChatsContext.chats.clear()
chatModel.secondaryChatsContext.popChatCollector.clear()
}
}
}
chatModel.users.clear()
@ -785,10 +786,10 @@ private fun afterSetCiTTL(
appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath)
withApi {
try {
withChats {
withContext(Dispatchers.Main) {
// 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())
updateChats(chats)
chatModel.chatsContext.updateChats(chats)
}
} catch (e: Exception) {
Log.e(TAG, "apiGetChats error: ${e.message}")

View file

@ -85,7 +85,7 @@ class ModalData(val keyboardCoversBar: Boolean = true) {
}
enum class ModalViewId {
GROUP_REPORTS
SECONDARY_CHAT
}
class ModalManager(private val placement: ModalPlacement? = null) {

View file

@ -18,7 +18,6 @@ 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
@ -30,6 +29,7 @@ import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import java.net.URI
@Composable
@ -42,10 +42,10 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c
withBGApi {
val groupInfo = chatModel.controller.apiNewGroup(rhId, incognito, groupProfile)
if (groupInfo != null) {
withChats {
updateGroup(rhId = rhId, groupInfo)
chatItems.clearAndNotify()
chatItemStatuses.clear()
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId = rhId, groupInfo)
chatModel.chatsContext.chatItems.clearAndNotify()
chatModel.chatsContext.chatItemStatuses.clear()
chatModel.chatId.value = groupInfo.id
}
setGroupMembers(rhId, groupInfo, chatModel)

View file

@ -7,13 +7,11 @@ 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.*
import chat.simplex.res.MR
import kotlinx.coroutines.*
import java.net.URI
enum class ConnectionLinkType {
INVITATION, CONTACT, GROUP
@ -359,8 +357,8 @@ suspend fun connectViaUri(
val pcc = chatModel.controller.apiConnect(rhId, incognito, uri)
val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION
if (pcc != null) {
withChats {
updateContactConnection(rhId, pcc)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnection(rhId, pcc)
}
close?.invoke()
AlertManager.privacySensitive.showAlertMsg(

View file

@ -5,7 +5,6 @@ import SectionDividerSpaced
import SectionTextFooter
import SectionView
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
@ -22,11 +21,11 @@ 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.*
import chat.simplex.res.MR
import kotlinx.coroutines.*
@Composable
fun ContactConnectionInfoView(
@ -185,8 +184,8 @@ fun DeleteButton(onClick: () -> Unit) {
private fun setContactAlias(rhId: Long?, contactConnection: PendingContactConnection, localAlias: String, chatModel: ChatModel) = withBGApi {
chatModel.controller.apiSetConnectionAlias(rhId, contactConnection.pccConnId, localAlias)?.let {
withChats {
updateContactConnection(rhId, it)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnection(rhId, it)
}
}
}

View file

@ -31,7 +31,6 @@ 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.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.topPaddingToContent
@ -39,7 +38,6 @@ import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
import kotlinx.coroutines.*
import java.net.URI
enum class NewChatOption {
INVITE, CONNECT
@ -315,8 +313,8 @@ fun ActiveProfilePicker(
if (contactConnection != null) {
updatedConn = controller.apiChangeConnectionUser(rhId, contactConnection.pccConnId, user.userId)
if (updatedConn != null) {
withChats {
updateContactConnection(rhId, updatedConn)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnection(rhId, updatedConn)
updateShownConnection(updatedConn)
}
}
@ -338,8 +336,8 @@ fun ActiveProfilePicker(
}
if (updatedConn != null) {
withChats {
updateContactConnection(user.remoteHostId, updatedConn)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnection(user.remoteHostId, updatedConn)
}
}
@ -368,8 +366,8 @@ fun ActiveProfilePicker(
appPreferences.incognito.set(true)
val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true)
if (conn != null) {
withChats {
updateContactConnection(rhId, conn)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnection(rhId, conn)
updateShownConnection(conn)
}
close()
@ -685,8 +683,8 @@ private fun createInvitation(
withBGApi {
val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get())
if (r != null) {
withChats {
updateContactConnection(rhId, r.second)
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateContactConnection(rhId, r.second)
chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false, conn = r.second)
contactConnection.value = r.second
}

View file

@ -11,13 +11,13 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
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.common.platform.chatModel
import chat.simplex.res.MR
import kotlinx.coroutines.*
@Composable
fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) {
@ -34,8 +34,8 @@ fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) {
if (updated != null) {
val (updatedProfile, updatedContacts) = updated
m.updateCurrentUser(user.remoteHostId, updatedProfile, preferences)
withChats {
updatedContacts.forEach { updateContact(user.remoteHostId, it) }
withContext(Dispatchers.Main) {
updatedContacts.forEach { chatModel.chatsContext.updateContact(user.remoteHostId, it) }
}
currentPreferences = preferences
}

View file

@ -30,8 +30,8 @@ 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 kotlinx.coroutines.*
enum class LAMode {
SYSTEM,
@ -119,15 +119,15 @@ fun PrivacySettingsView(
chatModel.currentUser.value = currentUser.copy(sendRcptsContacts = enable)
if (clearOverrides) {
// For loop here is to prevent ConcurrentModificationException that happens with forEach
withChats {
for (i in 0 until chats.size) {
val chat = chats[i]
withContext(Dispatchers.Main) {
for (i in 0 until chatModel.chatsContext.chats.size) {
val chat = chatModel.chatsContext.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)
chatModel.chatsContext.updateContact(currentUser.remoteHostId, contact)
}
}
}
@ -143,16 +143,16 @@ fun PrivacySettingsView(
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
chatModel.currentUser.value = currentUser.copy(sendRcptsSmallGroups = enable)
if (clearOverrides) {
withChats {
withContext(Dispatchers.Main) {
// For loop here is to prevent ConcurrentModificationException that happens with forEach
for (i in 0 until chats.size) {
val chat = chats[i]
for (i in 0 until chatModel.chatsContext.chats.size) {
val chat = chatModel.chatsContext.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)
chatModel.chatsContext.updateGroup(currentUser.remoteHostId, groupInfo)
}
}
}

View file

@ -14,8 +14,6 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.withChats
import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.DEFAULT_START_MODAL_WIDTH
import chat.simplex.common.ui.theme.SimpleXTheme
@ -58,12 +56,14 @@ fun showApp() {
} else {
// The last possible cause that can be closed
withApi {
withChats {
withContext(Dispatchers.Main) {
chatModel.chatId.value = null
chatItems.clearAndNotify()
chatModel.chatsContext.chatItems.clearAndNotify()
}
withReportsChatsIfOpen {
chatItems.clearAndNotify()
withContext(Dispatchers.Main) {
if (ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)) {
chatModel.secondaryChatsContext.chatItems.clearAndNotify()
}
}
}
}