mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-03-30 18:35:49 +00:00
android, desktop: moving chats changing in main thread (#5461)
* android, desktop: moving chats changing in main thread * modifying chat items in main thread only * comment
This commit is contained in:
committed by
GitHub
parent
e27f8a8d6a
commit
0dfcd60490
@@ -14,6 +14,7 @@ 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.*
|
||||
@@ -74,9 +75,13 @@ actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
|
||||
if (ModalManager.start.hasModalsOpen()) {
|
||||
ModalManager.start.closeModal()
|
||||
} else if (chatModel.chatId.value != null) {
|
||||
// Since no modals are open, the problem is probably in ChatView
|
||||
chatModel.chatId.value = null
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
withApi {
|
||||
withChats {
|
||||
// Since no modals are open, the problem is probably in ChatView
|
||||
chatModel.chatId.value = null
|
||||
chatItems.clearAndNotify()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ChatList, nothing to do. Maybe to show other view except ChatList
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ object ChatModel {
|
||||
val incompleteInitializedDbRemoved = mutableStateOf(false)
|
||||
private val _chats = mutableStateOf(SnapshotStateList<Chat>())
|
||||
val chats: State<List<Chat>> = _chats
|
||||
private val chatsContext = ChatsContext()
|
||||
// map of connections network statuses, key is agent connection id
|
||||
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
|
||||
val switchingUsersAndHosts = mutableStateOf(false)
|
||||
@@ -72,7 +71,10 @@ object ChatModel {
|
||||
* If some helper is missing, create it. Notify is needed to track state of items that we added manually (not via api call). See [apiLoadMessages].
|
||||
* If you use api call to get the items, use just [add] instead of [addAndNotify].
|
||||
* Never modify underlying list directly because it produces unexpected results in ChatView's LazyColumn (setting by index is ok) */
|
||||
val chatItems = mutableStateOf(SnapshotStateList<ChatItem>())
|
||||
private val _chatItems = mutableStateOf(SnapshotStateList<ChatItem>())
|
||||
val chatItems: State<SnapshotStateList<ChatItem>> = _chatItems
|
||||
// declaration of chatsContext should be after any other variable that is directly attached to ChatsContext class, otherwise, strange crash with NullPointerException for "this" parameter in random functions
|
||||
private val chatsContext = ChatsContext()
|
||||
// set listener here that will be notified on every add/delete of a chat item
|
||||
var chatItemsChangesListener: ChatItemsChangesListener? = null
|
||||
val chatState = ActiveChatState()
|
||||
@@ -157,7 +159,6 @@ object ChatModel {
|
||||
val updatingProgress = mutableStateOf(null as Float?)
|
||||
var updatingRequest: Closeable? = null
|
||||
|
||||
private val updatingChatsMutex: Mutex = Mutex()
|
||||
val changingActiveUserMutex: Mutex = Mutex()
|
||||
|
||||
val desktopNoUserNoRemote: Boolean @Composable get() = appPlatform.isDesktop && currentUser.value == null && currentRemoteHost.value == null
|
||||
@@ -336,12 +337,14 @@ object ChatModel {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> withChats(action: suspend ChatsContext.() -> T): T = updatingChatsMutex.withLock {
|
||||
// running everything inside the block on main thread. Make sure any heavy computation is moved to a background thread
|
||||
suspend fun <T> withChats(action: suspend ChatsContext.() -> T): T = withContext(Dispatchers.Main) {
|
||||
chatsContext.action()
|
||||
}
|
||||
|
||||
class ChatsContext {
|
||||
val chats = _chats
|
||||
val chatItems = _chatItems
|
||||
|
||||
suspend fun addChat(chat: Chat) {
|
||||
chats.add(index = 0, chat)
|
||||
@@ -762,7 +765,7 @@ object ChatModel {
|
||||
|
||||
suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem {
|
||||
val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct)
|
||||
withContext(Dispatchers.Main) {
|
||||
withChats {
|
||||
chatItems.addAndNotify(cItem)
|
||||
}
|
||||
return cItem
|
||||
@@ -770,7 +773,11 @@ object ChatModel {
|
||||
|
||||
fun removeLiveDummy() {
|
||||
if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
||||
chatItems.removeLastAndNotify()
|
||||
withApi {
|
||||
withChats {
|
||||
chatItems.removeLastAndNotify()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -891,19 +898,25 @@ object ChatModel {
|
||||
|
||||
fun replaceConnReqView(id: String, withId: String) {
|
||||
if (id == showingInvitation.value?.connId) {
|
||||
showingInvitation.value = null
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
chatModel.chatId.value = withId
|
||||
withApi {
|
||||
withChats {
|
||||
showingInvitation.value = null
|
||||
chatItems.clearAndNotify()
|
||||
chatModel.chatId.value = withId
|
||||
}
|
||||
}
|
||||
ModalManager.start.closeModals()
|
||||
ModalManager.end.closeModals()
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissConnReqView(id: String) {
|
||||
fun dismissConnReqView(id: String) = withApi {
|
||||
if (id == showingInvitation.value?.connId) {
|
||||
showingInvitation.value = null
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
chatModel.chatId.value = null
|
||||
withChats {
|
||||
showingInvitation.value = null
|
||||
chatItems.clearAndNotify()
|
||||
chatModel.chatId.value = null
|
||||
}
|
||||
// Close NewChatView
|
||||
ModalManager.start.closeModals()
|
||||
ModalManager.center.closeModals()
|
||||
|
||||
@@ -3044,8 +3044,8 @@ object ChatController {
|
||||
chatModel.users.addAll(users)
|
||||
chatModel.currentUser.value = user
|
||||
if (user == null) {
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
withChats {
|
||||
chatItems.clearAndNotify()
|
||||
chats.clear()
|
||||
popChatCollector.clear()
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ suspend fun apiLoadMessages(
|
||||
addChat(chat)
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
withChats {
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatItems.replaceAll(chat.chatItems)
|
||||
chatItems.replaceAll(chat.chatItems)
|
||||
chatModel.chatId.value = chat.chatInfo.id
|
||||
splits.value = newSplits
|
||||
if (chat.chatItems.isNotEmpty()) {
|
||||
@@ -70,8 +70,8 @@ suspend fun apiLoadMessages(
|
||||
)
|
||||
val insertAt = (indexInCurrentItems - (wasSize - newItems.size) + trimmedIds.size).coerceAtLeast(0)
|
||||
newItems.addAll(insertAt, chat.chatItems)
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatItems.replaceAll(newItems)
|
||||
withChats {
|
||||
chatItems.replaceAll(newItems)
|
||||
splits.value = newSplits
|
||||
chatState.moveUnreadAfterItem(oldUnreadSplitIndex, newUnreadSplitIndex, oldItems)
|
||||
}
|
||||
@@ -89,8 +89,8 @@ suspend fun apiLoadMessages(
|
||||
val indexToAdd = min(indexInCurrentItems + 1, newItems.size)
|
||||
val indexToAddIsLast = indexToAdd == newItems.size
|
||||
newItems.addAll(indexToAdd, chat.chatItems)
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatItems.replaceAll(newItems)
|
||||
withChats {
|
||||
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
|
||||
@@ -104,8 +104,8 @@ suspend fun apiLoadMessages(
|
||||
val newSplits = removeDuplicatesAndUpperSplits(newItems, chat, splits, visibleItemIndexesNonReversed)
|
||||
// currently, items will always be added on top, which is index 0
|
||||
newItems.addAll(0, chat.chatItems)
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatItems.replaceAll(newItems)
|
||||
withChats {
|
||||
chatItems.replaceAll(newItems)
|
||||
splits.value = listOf(chat.chatItems.last().id) + newSplits
|
||||
unreadAfterItemId.value = chat.chatItems.last().id
|
||||
totalAfter.value = navInfo.afterTotal
|
||||
@@ -119,8 +119,8 @@ suspend fun apiLoadMessages(
|
||||
newItems.addAll(oldItems)
|
||||
removeDuplicates(newItems, chat)
|
||||
newItems.addAll(chat.chatItems)
|
||||
withContext(Dispatchers.Main) {
|
||||
chatModel.chatItems.replaceAll(newItems)
|
||||
withChats {
|
||||
chatItems.replaceAll(newItems)
|
||||
unreadAfterNewestLoaded.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,7 +550,9 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
LaunchedEffect(chatInfo.id) {
|
||||
onComposed(chatInfo.id)
|
||||
ModalManager.end.closeModals()
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
withChats {
|
||||
chatItems.clearAndNotify()
|
||||
}
|
||||
}
|
||||
}
|
||||
is ChatInfo.InvalidJSON -> {
|
||||
@@ -561,7 +563,9 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
LaunchedEffect(chatInfo.id) {
|
||||
onComposed(chatInfo.id)
|
||||
ModalManager.end.closeModals()
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
withChats {
|
||||
chatItems.clearAndNotify()
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
|
||||
@@ -98,8 +98,8 @@ fun GroupMemberInfoView(
|
||||
val memberChat = Chat(remoteHostId = rhId, ChatInfo.Direct(memberContact), chatItems = arrayListOf())
|
||||
withChats {
|
||||
addChat(memberChat)
|
||||
openLoadedChat(memberChat)
|
||||
}
|
||||
openLoadedChat(memberChat)
|
||||
closeAll()
|
||||
chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected())
|
||||
}
|
||||
|
||||
@@ -216,15 +216,19 @@ suspend fun openChat(rhId: Long?, chatInfo: ChatInfo) = openChat(rhId, chatInfo.
|
||||
private suspend fun openChat(rhId: Long?, chatType: ChatType, apiId: Long) =
|
||||
apiLoadMessages(rhId, chatType, apiId, ChatPagination.Initial(ChatPagination.INITIAL_COUNT), chatModel.chatState)
|
||||
|
||||
fun openLoadedChat(chat: Chat) {
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatItems.replaceAll(chat.chatItems)
|
||||
chatModel.chatId.value = chat.chatInfo.id
|
||||
chatModel.chatState.clear()
|
||||
suspend fun openLoadedChat(chat: Chat) {
|
||||
withChats {
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatItems.replaceAll(chat.chatItems)
|
||||
chatModel.chatId.value = chat.chatInfo.id
|
||||
chatModel.chatState.clear()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiFindMessages(ch: Chat, search: String) {
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
withChats {
|
||||
chatItems.clearAndNotify()
|
||||
}
|
||||
apiLoadMessages(ch.remoteHostId, ch.chatInfo.chatType, ch.chatInfo.apiId, pagination = ChatPagination.Last(ChatPagination.INITIAL_COUNT), chatModel.chatState, search = search)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ fun onRequestAccepted(chat: Chat) {
|
||||
if (chatInfo is ChatInfo.Direct) {
|
||||
ModalManager.start.closeModals()
|
||||
if (chatInfo.contact.sndReady) {
|
||||
openLoadedChat(chat)
|
||||
withApi {
|
||||
openLoadedChat(chat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,9 +528,9 @@ fun deleteChatDatabaseFilesAndState() {
|
||||
|
||||
// Clear sensitive data on screen just in case ModalManager will fail to prevent hiding its modals while database encrypts itself
|
||||
chatModel.chatId.value = null
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
withLongRunningApi {
|
||||
withChats {
|
||||
chatItems.clearAndNotify()
|
||||
chats.clear()
|
||||
popChatCollector.clear()
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c
|
||||
if (groupInfo != null) {
|
||||
withChats {
|
||||
updateGroup(rhId = rhId, groupInfo)
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
chatItems.clearAndNotify()
|
||||
chatModel.chatItemStatuses.clear()
|
||||
chatModel.chatId.value = groupInfo.id
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ 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.platform.*
|
||||
import chat.simplex.common.ui.theme.DEFAULT_START_MODAL_WIDTH
|
||||
import chat.simplex.common.ui.theme.SimpleXTheme
|
||||
@@ -55,8 +56,12 @@ fun showApp() {
|
||||
// Better to not close fullscreen since it can contain passcode
|
||||
} else {
|
||||
// The last possible cause that can be closed
|
||||
chatModel.chatId.value = null
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
withApi {
|
||||
withChats {
|
||||
chatModel.chatId.value = null
|
||||
chatItems.clearAndNotify()
|
||||
}
|
||||
}
|
||||
}
|
||||
chatModel.activeCall.value?.let {
|
||||
withBGApi {
|
||||
|
||||
Reference in New Issue
Block a user