From 5b7de8f8c11b5bdaa0a0ff07dfb59a9bee4bcd88 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:20:10 +0000 Subject: [PATCH] desktop, android: pass remote host to API from the loaded objects, to prevent race conditions (#3397) * desktop, android: pass remote host explicitely to API calls * use remote host ID in model updates * add remote host to chat console * add remote host to notifications functions --- .../java/chat/simplex/app/MainActivity.kt | 2 +- .../main/java/chat/simplex/app/SimplexApp.kt | 2 +- .../common/views/call/CallView.android.kt | 11 +- .../newchat/ConnectViaLinkView.android.kt | 7 +- .../newchat/ScanToConnectView.android.kt | 3 +- .../ScanProtocolServer.android.kt | 4 +- .../kotlin/chat/simplex/common/App.kt | 2 +- .../chat/simplex/common/model/ChatModel.kt | 116 ++-- .../chat/simplex/common/model/SimpleXAPI.kt | 622 +++++++++--------- .../chat/simplex/common/platform/Core.kt | 2 +- .../simplex/common/platform/NtfManager.kt | 11 +- .../chat/simplex/common/views/TerminalView.kt | 11 +- .../chat/simplex/common/views/WelcomeView.kt | 9 +- .../simplex/common/views/call/CallManager.kt | 5 +- .../chat/simplex/common/views/call/WebRTC.kt | 10 +- .../simplex/common/views/chat/ChatInfoView.kt | 60 +- .../simplex/common/views/chat/ChatView.kt | 94 +-- .../simplex/common/views/chat/ComposeView.kt | 27 +- .../common/views/chat/ContactPreferences.kt | 5 +- .../views/chat/group/AddGroupMembersView.kt | 8 +- .../views/chat/group/GroupChatInfoView.kt | 55 +- .../common/views/chat/group/GroupLinkView.kt | 7 +- .../views/chat/group/GroupMemberInfoView.kt | 68 +- .../views/chat/group/GroupPreferences.kt | 12 +- .../views/chat/group/GroupProfileView.kt | 6 +- .../views/chat/group/WelcomeMessageView.kt | 6 +- .../views/chatlist/ChatListNavLinkView.kt | 157 ++--- .../common/views/chatlist/ChatListView.kt | 9 +- .../views/chatlist/ShareListNavLinkView.kt | 4 +- .../common/views/chatlist/UserPicker.kt | 4 +- .../common/views/database/DatabaseView.kt | 18 +- .../simplex/common/views/helpers/Utils.kt | 2 +- .../common/views/localauth/LocalAuthView.kt | 2 +- .../common/views/newchat/AddContactView.kt | 8 +- .../common/views/newchat/AddGroupView.kt | 12 +- .../views/newchat/ConnectViaLinkView.kt | 2 +- .../newchat/ContactConnectionInfoView.kt | 11 +- .../common/views/newchat/CreateLinkView.kt | 11 +- .../common/views/newchat/NewChatSheet.kt | 9 +- .../common/views/newchat/PasteToConnect.kt | 7 +- .../common/views/newchat/ScanToConnectView.kt | 82 +-- .../views/onboarding/CreateSimpleXAddress.kt | 16 +- .../onboarding/SetupDatabasePassphrase.kt | 2 +- .../views/usersettings/HiddenProfileView.kt | 2 +- .../views/usersettings/NetworkAndServers.kt | 4 +- .../common/views/usersettings/Preferences.kt | 6 +- .../views/usersettings/PrivacySettings.kt | 8 +- .../views/usersettings/ProtocolServerView.kt | 2 +- .../views/usersettings/ProtocolServersView.kt | 21 +- .../views/usersettings/ScanProtocolServer.kt | 6 +- .../usersettings/SetDeliveryReceiptsView.kt | 4 +- .../common/views/usersettings/SettingsView.kt | 2 +- .../views/usersettings/UserAddressView.kt | 10 +- .../views/usersettings/UserProfileView.kt | 4 +- .../views/usersettings/UserProfilesView.kt | 14 +- .../common/views/call/CallView.desktop.kt | 11 +- .../views/chatlist/ChatListView.desktop.kt | 2 +- .../newchat/ConnectViaLinkView.desktop.kt | 5 +- .../newchat/ScanToConnectView.desktop.kt | 3 +- .../ScanProtocolServer.desktop.kt | 4 +- 60 files changed, 853 insertions(+), 776 deletions(-) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt index 40c04f5088..4c5d595a83 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt @@ -126,7 +126,7 @@ fun processIntent(intent: Intent?) { when (intent?.action) { "android.intent.action.VIEW" -> { val uri = intent.data - if (uri != null) connectIfOpenedViaUri(uri.toURI(), ChatModel) + if (uri != null) connectIfOpenedViaUri(chatModel.remoteHostId, uri.toURI(), ChatModel) } } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index d2c4465172..13908f69bf 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -57,7 +57,7 @@ class SimplexApp: Application(), LifecycleEventObserver { updatingChatsMutex.withLock { kotlin.runCatching { val currentUserId = chatModel.currentUser.value?.userId - val chats = ArrayList(chatController.apiGetChats()) + val chats = ArrayList(chatController.apiGetChats(chatModel.remoteHostId)) /** Active user can be changed in background while [ChatController.apiGetChats] is executing */ if (chatModel.currentUser.value?.userId == currentUserId) { val currentChatId = chatModel.chatId.value diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index 5c7b430ab3..c173463d5e 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -115,22 +115,23 @@ actual fun ActiveCallView() { val call = chatModel.activeCall.value if (call != null) { Log.d(TAG, "has active call $call") + val callRh = call.remoteHostId when (val r = apiMsg.resp) { is WCallResponse.Capabilities -> withBGApi { val callType = CallType(call.localMedia, r.capabilities) - chatModel.controller.apiSendCallInvitation(call.contact, callType) + chatModel.controller.apiSendCallInvitation(callRh, call.contact, callType) chatModel.activeCall.value = call.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) } is WCallResponse.Offer -> withBGApi { - chatModel.controller.apiSendCallOffer(call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities) + chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities) chatModel.activeCall.value = call.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities) } is WCallResponse.Answer -> withBGApi { - chatModel.controller.apiSendCallAnswer(call.contact, r.answer, r.iceCandidates) + chatModel.controller.apiSendCallAnswer(callRh, call.contact, r.answer, r.iceCandidates) chatModel.activeCall.value = call.copy(callState = CallState.Negotiated) } is WCallResponse.Ice -> withBGApi { - chatModel.controller.apiSendCallExtraInfo(call.contact, r.iceCandidates) + chatModel.controller.apiSendCallExtraInfo(callRh, call.contact, r.iceCandidates) } is WCallResponse.Connection -> try { @@ -139,7 +140,7 @@ actual fun ActiveCallView() { chatModel.activeCall.value = call.copy(callState = CallState.Connected, connectedAt = Clock.System.now()) setCallSound(call.soundSpeaker, audioViaBluetooth) } - withBGApi { chatModel.controller.apiCallStatus(call.contact, callStatus) } + withBGApi { chatModel.controller.apiCallStatus(callRh, call.contact, callStatus) } } catch (e: Error) { Log.d(TAG,"call status ${r.state.connectionState} not used") } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt index dcb5055425..1faf115b37 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.android.kt @@ -12,7 +12,8 @@ import chat.simplex.common.model.ChatModel import chat.simplex.res.MR @Composable -actual fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) { +actual fun ConnectViaLinkView(m: ChatModel, rhId: Long?, close: () -> Unit) { + // TODO this should close if remote host changes in model val selection = remember { mutableStateOf( runCatching { ConnectViaLinkTab.valueOf(m.controller.appPrefs.connectViaLinkTab.get()!!) }.getOrDefault(ConnectViaLinkTab.SCAN) @@ -31,10 +32,10 @@ actual fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) { Column(Modifier.weight(1f)) { when (selection.value) { ConnectViaLinkTab.SCAN -> { - ScanToConnectView(m, close) + ScanToConnectView(m, rhId, close) } ConnectViaLinkTab.PASTE -> { - PasteToConnectView(m, close) + PasteToConnectView(m, rhId, close) } } } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt index d0cad31210..89477e45a1 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.android.kt @@ -7,13 +7,14 @@ import chat.simplex.common.model.ChatModel import com.google.accompanist.permissions.rememberPermissionState @Composable -actual fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { +actual fun ScanToConnectView(chatModel: ChatModel, rhId: Long?, close: () -> Unit) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) LaunchedEffect(Unit) { cameraPermissionState.launchPermissionRequest() } ConnectContactLayout( chatModel = chatModel, + rhId = rhId, incognitoPref = chatModel.controller.appPrefs.incognito, close = close ) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt index a1b7b3141d..af5a27be11 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.android.kt @@ -7,10 +7,10 @@ import chat.simplex.common.model.ServerCfg import com.google.accompanist.permissions.rememberPermissionState @Composable -actual fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) { +actual fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) { val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA) LaunchedEffect(Unit) { cameraPermissionState.launchPermissionRequest() } - ScanProtocolServerLayout(onNext) + ScanProtocolServerLayout(rhId, onNext) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 41deee7a55..da74a37aaa 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -138,7 +138,7 @@ fun MainScreen() { } onboarding == OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {} onboarding == OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) - onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel) + onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel, null) onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) } if (appPlatform.isAndroid) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index af946be3cd..ab8b6af3fd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -111,6 +111,7 @@ object ChatModel { // remote controller val remoteHosts = mutableStateListOf() val currentRemoteHost = mutableStateOf(null) + val remoteHostId: Long? get() = currentRemoteHost?.value?.remoteHostId val newRemoteHostPairing = mutableStateOf?>(null) val remoteCtrlSession = mutableStateOf(null) @@ -141,16 +142,17 @@ object ChatModel { } // toList() here is to prevent ConcurrentModificationException that is rarely happens but happens - fun hasChat(id: String): Boolean = chats.toList().firstOrNull { it.id == id } != null + fun hasChat(rhId: Long?, id: String): Boolean = chats.toList().firstOrNull { it.id == id && it.remoteHostId == rhId } != null + // TODO pass rhId? fun getChat(id: String): Chat? = chats.toList().firstOrNull { it.id == id } fun getContactChat(contactId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId } fun getGroupChat(groupId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Group && it.chatInfo.apiId == groupId } fun getGroupMember(groupMemberId: Long): GroupMember? = groupMembers.firstOrNull { it.groupMemberId == groupMemberId } - private fun getChatIndex(id: String): Int = chats.toList().indexOfFirst { it.id == id } + private fun getChatIndex(rhId: Long?, id: String): Int = chats.toList().indexOfFirst { it.id == id && it.remoteHostId == rhId } fun addChat(chat: Chat) = chats.add(index = 0, chat) - fun updateChatInfo(cInfo: ChatInfo) { - val i = getChatIndex(cInfo.id) + fun updateChatInfo(rhId: Long?, cInfo: ChatInfo) { + val i = getChatIndex(rhId, cInfo.id) if (i >= 0) { val currentCInfo = chats[i].chatInfo var newCInfo = cInfo @@ -172,23 +174,23 @@ object ChatModel { } } - fun updateContactConnection(contactConnection: PendingContactConnection) = updateChat(ChatInfo.ContactConnection(contactConnection)) + fun updateContactConnection(rhId: Long?, contactConnection: PendingContactConnection) = updateChat(rhId, ChatInfo.ContactConnection(contactConnection)) - fun updateContact(contact: Contact) = updateChat(ChatInfo.Direct(contact), addMissing = contact.directOrUsed) + fun updateContact(rhId: Long?, contact: Contact) = updateChat(rhId, ChatInfo.Direct(contact), addMissing = contact.directOrUsed) - fun updateContactConnectionStats(contact: Contact, connectionStats: ConnectionStats) { + fun updateContactConnectionStats(rhId: Long?, contact: Contact, connectionStats: ConnectionStats) { val updatedConn = contact.activeConn?.copy(connectionStats = connectionStats) val updatedContact = contact.copy(activeConn = updatedConn) - updateContact(updatedContact) + updateContact(rhId, updatedContact) } - fun updateGroup(groupInfo: GroupInfo) = updateChat(ChatInfo.Group(groupInfo)) + fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo)) - private fun updateChat(cInfo: ChatInfo, addMissing: Boolean = true) { - if (hasChat(cInfo.id)) { - updateChatInfo(cInfo) + private fun updateChat(rhId: Long?, cInfo: ChatInfo, addMissing: Boolean = true) { + if (hasChat(rhId, cInfo.id)) { + updateChatInfo(rhId, cInfo) } else if (addMissing) { - addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf())) + addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf())) } } @@ -203,8 +205,8 @@ object ChatModel { } } - fun replaceChat(id: String, chat: Chat) { - val i = getChatIndex(id) + fun replaceChat(rhId: Long?, id: String, chat: Chat) { + val i = getChatIndex(rhId, id) if (i >= 0) { chats[i] = chat } else { @@ -213,9 +215,9 @@ object ChatModel { } } - suspend fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) = updatingChatsMutex.withLock { + suspend fun addChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) = updatingChatsMutex.withLock { // update previews - val i = getChatIndex(cInfo.id) + val i = getChatIndex(rhId, cInfo.id) val chat: Chat if (i >= 0) { chat = chats[i] @@ -224,7 +226,7 @@ object ChatModel { chatStats = if (cItem.meta.itemStatus is CIStatus.RcvNew) { val minUnreadId = if(chat.chatStats.minUnreadItemId == 0L) cItem.id else chat.chatStats.minUnreadItemId - increaseUnreadCounter(currentUser.value!!) + increaseUnreadCounter(rhId, currentUser.value!!) chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, minUnreadItemId = minUnreadId) } else @@ -234,7 +236,7 @@ object ChatModel { popChat_(i) } } else { - addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf(cItem))) + addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem))) } Log.d(TAG, "TODOCHAT: addChatItem: adding to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}") withContext(Dispatchers.Main) { @@ -254,9 +256,9 @@ object ChatModel { } } - suspend fun upsertChatItem(cInfo: ChatInfo, cItem: ChatItem): Boolean = updatingChatsMutex.withLock { + suspend fun upsertChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem): Boolean = updatingChatsMutex.withLock { // update previews - val i = getChatIndex(cInfo.id) + val i = getChatIndex(rhId, cInfo.id) val chat: Chat val res: Boolean if (i >= 0) { @@ -266,12 +268,12 @@ object ChatModel { chats[i] = chat.copy(chatItems = arrayListOf(cItem)) if (pItem.isRcvNew && !cItem.isRcvNew) { // status changed from New to Read, update counter - decreaseCounterInChat(cInfo.id) + decreaseCounterInChat(rhId, cInfo.id) } } res = false } else { - addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf(cItem))) + addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem))) res = true } Log.d(TAG, "TODOCHAT: upsertChatItem: upserting to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}") @@ -313,12 +315,12 @@ object ChatModel { } } - fun removeChatItem(cInfo: ChatInfo, cItem: ChatItem) { + fun removeChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) { if (cItem.isRcvNew) { - decreaseCounterInChat(cInfo.id) + decreaseCounterInChat(rhId, cInfo.id) } // update previews - val i = getChatIndex(cInfo.id) + val i = getChatIndex(rhId, cInfo.id) val chat: Chat if (i >= 0) { chat = chats[i] @@ -337,11 +339,11 @@ object ChatModel { } } - fun clearChat(cInfo: ChatInfo) { + fun clearChat(rhId: Long?, cInfo: ChatInfo) { // clear preview - val i = getChatIndex(cInfo.id) + val i = getChatIndex(rhId, cInfo.id) if (i >= 0) { - decreaseUnreadCounter(currentUser.value!!, chats[i].chatStats.unreadCount) + decreaseUnreadCounter(rhId, currentUser.value!!, chats[i].chatStats.unreadCount) chats[i] = chats[i].copy(chatItems = arrayListOf(), chatStats = Chat.ChatStats(), chatInfo = cInfo) } // clear current chat @@ -351,15 +353,15 @@ object ChatModel { } } - fun updateCurrentUser(newProfile: Profile, preferences: FullChatPreferences? = null) { + fun updateCurrentUser(rhId: Long?, newProfile: Profile, preferences: FullChatPreferences? = null) { val current = currentUser.value ?: return val updated = current.copy( profile = newProfile.toLocalProfile(current.profile.profileId), fullPreferences = preferences ?: current.fullPreferences ) - val indexInUsers = users.indexOfFirst { it.user.userId == current.userId } - if (indexInUsers != -1) { - users[indexInUsers] = UserInfo(updated, users[indexInUsers].unreadCount) + val i = users.indexOfFirst { it.user.userId == current.userId && it.user.remoteHostId == rhId } + if (i != -1) { + users[i] = users[i].copy(user = updated) } currentUser.value = updated } @@ -378,16 +380,17 @@ object ChatModel { } } - fun markChatItemsRead(cInfo: ChatInfo, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) { - val markedRead = markItemsReadInCurrentChat(cInfo, range) + fun markChatItemsRead(chat: Chat, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) { + val cInfo = chat.chatInfo + val markedRead = markItemsReadInCurrentChat(chat, range) // update preview - val chatIdx = getChatIndex(cInfo.id) + val chatIdx = getChatIndex(chat.remoteHostId, cInfo.id) if (chatIdx >= 0) { val chat = chats[chatIdx] val lastId = chat.chatItems.lastOrNull()?.id if (lastId != null) { val unreadCount = unreadCountAfter ?: if (range != null) chat.chatStats.unreadCount - markedRead else 0 - decreaseUnreadCounter(currentUser.value!!, chat.chatStats.unreadCount - unreadCount) + decreaseUnreadCounter(chat.remoteHostId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount) chats[chatIdx] = chat.copy( chatStats = chat.chatStats.copy( unreadCount = unreadCount, @@ -399,7 +402,8 @@ object ChatModel { } } - private fun markItemsReadInCurrentChat(cInfo: ChatInfo, range: CC.ItemRange? = null): Int { + private fun markItemsReadInCurrentChat(chat: Chat, range: CC.ItemRange? = null): Int { + val cInfo = chat.chatInfo var markedRead = 0 if (chatId.value == cInfo.id) { var i = 0 @@ -423,13 +427,13 @@ object ChatModel { return markedRead } - private fun decreaseCounterInChat(chatId: ChatId) { - val chatIndex = getChatIndex(chatId) + private fun decreaseCounterInChat(rhId: Long?, chatId: ChatId) { + val chatIndex = getChatIndex(rhId, chatId) if (chatIndex == -1) return val chat = chats[chatIndex] val unreadCount = kotlin.math.max(chat.chatStats.unreadCount - 1, 0) - decreaseUnreadCounter(currentUser.value!!, chat.chatStats.unreadCount - unreadCount) + decreaseUnreadCounter(rhId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount) chats[chatIndex] = chat.copy( chatStats = chat.chatStats.copy( unreadCount = unreadCount, @@ -437,18 +441,18 @@ object ChatModel { ) } - fun increaseUnreadCounter(user: UserLike) { - changeUnreadCounter(user, 1) + fun increaseUnreadCounter(rhId: Long?, user: UserLike) { + changeUnreadCounter(rhId, user, 1) } - fun decreaseUnreadCounter(user: UserLike, by: Int = 1) { - changeUnreadCounter(user, -by) + fun decreaseUnreadCounter(rhId: Long?, user: UserLike, by: Int = 1) { + changeUnreadCounter(rhId, user, -by) } - private fun changeUnreadCounter(user: UserLike, by: Int) { - val i = users.indexOfFirst { it.user.userId == user.userId } + private fun changeUnreadCounter(rhId: Long?, user: UserLike, by: Int) { + val i = users.indexOfFirst { it.user.userId == user.userId && it.user.remoteHostId == rhId } if (i != -1) { - users[i] = UserInfo(users[i].user, users[i].unreadCount + by) + users[i] = users[i].copy(unreadCount = users[i].unreadCount + by) } } @@ -544,14 +548,14 @@ object ChatModel { } } - fun removeChat(id: String) { - chats.removeAll { it.id == id } + fun removeChat(rhId: Long?, id: String) { + chats.removeAll { it.id == id && it.remoteHostId == rhId } } - fun upsertGroupMember(groupInfo: GroupInfo, member: GroupMember): Boolean { + fun upsertGroupMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember): Boolean { // user member was updated if (groupInfo.membership.groupMemberId == member.groupMemberId) { - updateGroup(groupInfo) + updateGroup(rhId, groupInfo) return false } // update current chat @@ -569,12 +573,12 @@ object ChatModel { } } - fun updateGroupMemberConnectionStats(groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) { + fun updateGroupMemberConnectionStats(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) { val memberConn = member.activeConn if (memberConn != null) { val updatedConn = memberConn.copy(connectionStats = connectionStats) val updatedMember = member.copy(activeConn = updatedConn) - upsertGroupMember(groupInfo, updatedMember) + upsertGroupMember(rhId, groupInfo, updatedMember) } } @@ -612,6 +616,7 @@ enum class ChatType(val type: String) { @Serializable data class User( + val remoteHostId: Long? = null, override val userId: Long, val userContactId: Long, val localDisplayName: String, @@ -711,9 +716,10 @@ interface SomeChat { @Serializable @Stable data class Chat ( + val remoteHostId: Long? = null, val chatInfo: ChatInfo, val chatItems: List, - val chatStats: ChatStats = ChatStats(), + val chatStats: ChatStats = ChatStats() ) { val userCanSend: Boolean get() = when (chatInfo) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 253a9fb163..ffb5b4251b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -4,6 +4,7 @@ import chat.simplex.common.views.helpers.* import androidx.compose.runtime.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter +import chat.simplex.common.model.ChatModel.remoteHostId import chat.simplex.common.model.ChatModel.updatingChatsMutex import dev.icerock.moko.resources.compose.painterResource import chat.simplex.common.platform.* @@ -352,13 +353,13 @@ object ChatController { apiSetXFTPConfig(getXFTPCfg()) apiSetEncryptLocalFiles(appPrefs.privacyEncryptLocalFiles.get()) val justStarted = apiStartChat() - val users = listUsers() + val users = listUsers(null) chatModel.users.clear() chatModel.users.addAll(users) if (justStarted) { chatModel.currentUser.value = user chatModel.userCreated.value = true - getUserChatData() + getUserChatData(null) appPrefs.chatLastStart.set(Clock.System.now()) chatModel.chatRunning.value = true startReceiver() @@ -366,7 +367,7 @@ object ChatController { Log.d(TAG, "startChat: started") } else { updatingChatsMutex.withLock { - val chats = apiGetChats() + val chats = apiGetChats(null) chatModel.updateChats(chats) } Log.d(TAG, "startChat: running") @@ -377,33 +378,33 @@ object ChatController { } } - suspend fun changeActiveUser(toUserId: Long, viewPwd: String?) { + suspend fun changeActiveUser(rhId: Long?, toUserId: Long, viewPwd: String?) { try { - changeActiveUser_(toUserId, viewPwd) + changeActiveUser_(rhId, toUserId, viewPwd) } catch (e: Exception) { Log.e(TAG, "Unable to set active user: ${e.stackTraceToString()}") AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_active_user_title), e.stackTraceToString()) } } - suspend fun changeActiveUser_(toUserId: Long, viewPwd: String?) { - val currentUser = apiSetActiveUser(toUserId, viewPwd) + suspend fun changeActiveUser_(rhId: Long?, toUserId: Long, viewPwd: String?) { + val currentUser = apiSetActiveUser(rhId, toUserId, viewPwd) chatModel.currentUser.value = currentUser - val users = listUsers() + val users = listUsers(rhId) chatModel.users.clear() chatModel.users.addAll(users) - getUserChatData() + getUserChatData(rhId) val invitation = chatModel.callInvitations.values.firstOrNull { inv -> inv.user.userId == toUserId } if (invitation != null) { chatModel.callManager.reportNewIncomingCall(invitation.copy(user = currentUser)) } } - suspend fun getUserChatData() { - chatModel.userAddress.value = apiGetUserAddress() - chatModel.chatItemTTL.value = getChatItemTTL() + suspend fun getUserChatData(rhId: Long?) { + chatModel.userAddress.value = apiGetUserAddress(rhId) + chatModel.chatItemTTL.value = getChatItemTTL(rhId) updatingChatsMutex.withLock { - val chats = apiGetChats() + val chats = apiGetChats(rhId) chatModel.updateChats(chats) } } @@ -428,21 +429,20 @@ object ChatController { } } - suspend fun sendCmd(cmd: CC, customRhId: Long? = null): CR { + suspend fun sendCmd(rhId: Long?, cmd: CC): CR { val ctrl = ctrl ?: throw Exception("Controller is not initialized") return withContext(Dispatchers.IO) { val c = cmd.cmdString - chatModel.addTerminalItem(TerminalItem.cmd(cmd.obfuscated)) + chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated)) Log.d(TAG, "sendCmd: ${cmd.cmdType}") - val rhId = customRhId?.toInt() ?: chatModel.currentRemoteHost.value?.remoteHostId?.toInt() ?: -1 - val json = if (rhId == -1) chatSendCmd(ctrl, c) else chatSendRemoteCmd(ctrl, rhId, c) + val json = if (rhId == null) chatSendCmd(ctrl, c) else chatSendRemoteCmd(ctrl, rhId.toInt(), c) val r = APIResponse.decodeStr(json) Log.d(TAG, "sendCmd response type ${r.resp.responseType}") if (r.resp is CR.Response || r.resp is CR.Invalid) { Log.d(TAG, "sendCmd response json $json") } - chatModel.addTerminalItem(TerminalItem.resp(r.resp)) + chatModel.addTerminalItem(TerminalItem.resp(rhId, r.resp)) r.resp } } @@ -460,16 +460,16 @@ object ChatController { } } - suspend fun apiGetActiveUser(): User? { - val r = sendCmd(CC.ShowActiveUser()) + suspend fun apiGetActiveUser(rh: Long?): User? { + val r = sendCmd(rh, CC.ShowActiveUser()) if (r is CR.ActiveUser) return r.user Log.d(TAG, "apiGetActiveUser: ${r.responseType} ${r.details}") chatModel.userCreated.value = false return null } - suspend fun apiCreateActiveUser(p: Profile?, sameServers: Boolean = false, pastTimestamp: Boolean = false): User? { - val r = sendCmd(CC.CreateActiveUser(p, sameServers = sameServers, pastTimestamp = pastTimestamp)) + suspend fun apiCreateActiveUser(rh: Long?, p: Profile?, sameServers: Boolean = false, pastTimestamp: Boolean = false): User? { + val r = sendCmd(rh, CC.CreateActiveUser(p, sameServers = sameServers, pastTimestamp = pastTimestamp)) if (r is CR.ActiveUser) return r.user else if ( r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.DuplicateName || @@ -483,65 +483,68 @@ object ChatController { return null } - suspend fun listUsers(): List { - val r = sendCmd(CC.ListUsers()) - if (r is CR.UsersList) return r.users.sortedBy { it.user.chatViewName } + suspend fun listUsers(rh: Long?): List { + val r = sendCmd(rh, CC.ListUsers()) + if (r is CR.UsersList) { + val users = if (rh == null) r.users else r.users.map { it.copy(user = it.user.copy(remoteHostId = rh)) } + return users.sortedBy { it.user.chatViewName } + } Log.d(TAG, "listUsers: ${r.responseType} ${r.details}") throw Exception("failed to list users ${r.responseType} ${r.details}") } - suspend fun apiSetActiveUser(userId: Long, viewPwd: String?): User { - val r = sendCmd(CC.ApiSetActiveUser(userId, viewPwd)) - if (r is CR.ActiveUser) return r.user + suspend fun apiSetActiveUser(rh: Long?, userId: Long, viewPwd: String?): User { + val r = sendCmd(rh, CC.ApiSetActiveUser(userId, viewPwd)) + if (r is CR.ActiveUser) return if (rh == null) r.user else r.user.copy(remoteHostId = rh) Log.d(TAG, "apiSetActiveUser: ${r.responseType} ${r.details}") throw Exception("failed to set the user as active ${r.responseType} ${r.details}") } - suspend fun apiSetAllContactReceipts(enable: Boolean) { - val r = sendCmd(CC.SetAllContactReceipts(enable)) + suspend fun apiSetAllContactReceipts(rh: Long?, enable: Boolean) { + val r = sendCmd(rh, CC.SetAllContactReceipts(enable)) if (r is CR.CmdOk) return throw Exception("failed to set receipts for all users ${r.responseType} ${r.details}") } - suspend fun apiSetUserContactReceipts(userId: Long, userMsgReceiptSettings: UserMsgReceiptSettings) { - val r = sendCmd(CC.ApiSetUserContactReceipts(userId, userMsgReceiptSettings)) + suspend fun apiSetUserContactReceipts(u: User, userMsgReceiptSettings: UserMsgReceiptSettings) { + val r = sendCmd(u.remoteHostId, CC.ApiSetUserContactReceipts(u.userId, userMsgReceiptSettings)) if (r is CR.CmdOk) return throw Exception("failed to set receipts for user contacts ${r.responseType} ${r.details}") } - suspend fun apiSetUserGroupReceipts(userId: Long, userMsgReceiptSettings: UserMsgReceiptSettings) { - val r = sendCmd(CC.ApiSetUserGroupReceipts(userId, userMsgReceiptSettings)) + suspend fun apiSetUserGroupReceipts(u: User, userMsgReceiptSettings: UserMsgReceiptSettings) { + val r = sendCmd(u.remoteHostId, CC.ApiSetUserGroupReceipts(u.userId, userMsgReceiptSettings)) if (r is CR.CmdOk) return throw Exception("failed to set receipts for user groups ${r.responseType} ${r.details}") } - suspend fun apiHideUser(userId: Long, viewPwd: String): User = - setUserPrivacy(CC.ApiHideUser(userId, viewPwd)) + suspend fun apiHideUser(u: User, viewPwd: String): User = + setUserPrivacy(u.remoteHostId, CC.ApiHideUser(u.userId, viewPwd)) - suspend fun apiUnhideUser(userId: Long, viewPwd: String): User = - setUserPrivacy(CC.ApiUnhideUser(userId, viewPwd)) + suspend fun apiUnhideUser(u: User, viewPwd: String): User = + setUserPrivacy(u.remoteHostId, CC.ApiUnhideUser(u.userId, viewPwd)) - suspend fun apiMuteUser(userId: Long): User = - setUserPrivacy(CC.ApiMuteUser(userId)) + suspend fun apiMuteUser(u: User): User = + setUserPrivacy(u.remoteHostId, CC.ApiMuteUser(u.userId)) - suspend fun apiUnmuteUser(userId: Long): User = - setUserPrivacy(CC.ApiUnmuteUser(userId)) + suspend fun apiUnmuteUser(u: User): User = + setUserPrivacy(u.remoteHostId, CC.ApiUnmuteUser(u.userId)) - private suspend fun setUserPrivacy(cmd: CC): User { - val r = sendCmd(cmd) - if (r is CR.UserPrivacy) return r.updatedUser + private suspend fun setUserPrivacy(rh: Long?, cmd: CC): User { + val r = sendCmd(rh, cmd) + if (r is CR.UserPrivacy) return if (rh == null) r.updatedUser else r.updatedUser.copy(remoteHostId = rh) else throw Exception("Failed to change user privacy: ${r.responseType} ${r.details}") } - suspend fun apiDeleteUser(userId: Long, delSMPQueues: Boolean, viewPwd: String?) { - val r = sendCmd(CC.ApiDeleteUser(userId, delSMPQueues, viewPwd)) + suspend fun apiDeleteUser(u: User, delSMPQueues: Boolean, viewPwd: String?) { + val r = sendCmd(u.remoteHostId, CC.ApiDeleteUser(u.userId, delSMPQueues, viewPwd)) if (r is CR.CmdOk) return Log.d(TAG, "apiDeleteUser: ${r.responseType} ${r.details}") throw Exception("failed to delete the user ${r.responseType} ${r.details}") } suspend fun apiStartChat(): Boolean { - val r = sendCmd(CC.StartChat(expire = true)) + val r = sendCmd(null, CC.StartChat(expire = true)) when (r) { is CR.ChatStarted -> return true is CR.ChatRunning -> return false @@ -550,7 +553,7 @@ object ChatController { } suspend fun apiStopChat(): Boolean { - val r = sendCmd(CC.ApiStopChat()) + val r = sendCmd(null, CC.ApiStopChat()) when (r) { is CR.ChatStopped -> return true else -> throw Error("failed stopping chat: ${r.responseType} ${r.details}") @@ -558,76 +561,76 @@ object ChatController { } private suspend fun apiSetTempFolder(tempFolder: String) { - val r = sendCmd(CC.SetTempFolder(tempFolder)) + val r = sendCmd(null, CC.SetTempFolder(tempFolder)) if (r is CR.CmdOk) return throw Error("failed to set temp folder: ${r.responseType} ${r.details}") } private suspend fun apiSetFilesFolder(filesFolder: String) { - val r = sendCmd(CC.SetFilesFolder(filesFolder)) + val r = sendCmd(null, CC.SetFilesFolder(filesFolder)) if (r is CR.CmdOk) return throw Error("failed to set files folder: ${r.responseType} ${r.details}") } private suspend fun apiSetRemoteHostsFolder(remoteHostsFolder: String) { - val r = sendCmd(CC.SetRemoteHostsFolder(remoteHostsFolder)) + val r = sendCmd(null, CC.SetRemoteHostsFolder(remoteHostsFolder)) if (r is CR.CmdOk) return throw Error("failed to set remote hosts folder: ${r.responseType} ${r.details}") } suspend fun apiSetXFTPConfig(cfg: XFTPFileConfig?) { - val r = sendCmd(CC.ApiSetXFTPConfig(cfg)) + val r = sendCmd(null, CC.ApiSetXFTPConfig(cfg)) if (r is CR.CmdOk) return throw Error("apiSetXFTPConfig bad response: ${r.responseType} ${r.details}") } - suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(CC.ApiSetEncryptLocalFiles(enable)) + suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(null, CC.ApiSetEncryptLocalFiles(enable)) suspend fun apiExportArchive(config: ArchiveConfig) { - val r = sendCmd(CC.ApiExportArchive(config)) + val r = sendCmd(null, CC.ApiExportArchive(config)) if (r is CR.CmdOk) return throw Error("failed to export archive: ${r.responseType} ${r.details}") } suspend fun apiImportArchive(config: ArchiveConfig): List { - val r = sendCmd(CC.ApiImportArchive(config)) + val r = sendCmd(null, CC.ApiImportArchive(config)) if (r is CR.ArchiveImported) return r.archiveErrors throw Error("failed to import archive: ${r.responseType} ${r.details}") } suspend fun apiDeleteStorage() { - val r = sendCmd(CC.ApiDeleteStorage()) + val r = sendCmd(null, CC.ApiDeleteStorage()) if (r is CR.CmdOk) return throw Error("failed to delete storage: ${r.responseType} ${r.details}") } suspend fun apiStorageEncryption(currentKey: String = "", newKey: String = ""): CR.ChatCmdError? { - val r = sendCmd(CC.ApiStorageEncryption(DBEncryptionConfig(currentKey, newKey))) + val r = sendCmd(null, CC.ApiStorageEncryption(DBEncryptionConfig(currentKey, newKey))) if (r is CR.CmdOk) return null else if (r is CR.ChatCmdError) return r throw Exception("failed to set storage encryption: ${r.responseType} ${r.details}") } - suspend fun apiGetChats(): List { + suspend fun apiGetChats(rh: Long?): List { val userId = kotlin.runCatching { currentUserId("apiGetChats") }.getOrElse { return emptyList() } - val r = sendCmd(CC.ApiGetChats(userId)) - if (r is CR.ApiChats) return r.chats + val r = sendCmd(rh, CC.ApiGetChats(userId)) + if (r is CR.ApiChats) return if (rh == null) r.chats else r.chats.map { it.copy(remoteHostId = rh) } Log.e(TAG, "failed getting the list of chats: ${r.responseType} ${r.details}") AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_parse_chats_title), generalGetString(MR.strings.contact_developers)) return emptyList() } - suspend fun apiGetChat(type: ChatType, id: Long, pagination: ChatPagination = ChatPagination.Last(ChatPagination.INITIAL_COUNT), search: String = ""): Chat? { - val r = sendCmd(CC.ApiGetChat(type, id, pagination, search)) - if (r is CR.ApiChat) return r.chat + suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, pagination: ChatPagination = ChatPagination.Last(ChatPagination.INITIAL_COUNT), search: String = ""): Chat? { + val r = sendCmd(rh, CC.ApiGetChat(type, id, pagination, search)) + if (r is CR.ApiChat) return if (rh == null) r.chat else r.chat.copy(remoteHostId = rh) Log.e(TAG, "apiGetChat bad response: ${r.responseType} ${r.details}") AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_parse_chat_title), generalGetString(MR.strings.contact_developers)) return null } - suspend fun apiSendMessage(rhId: Long?, type: ChatType, id: Long, file: CryptoFile? = null, quotedItemId: Long? = null, mc: MsgContent, live: Boolean = false, ttl: Int? = null): AChatItem? { + suspend fun apiSendMessage(rh: Long?, type: ChatType, id: Long, file: CryptoFile? = null, quotedItemId: Long? = null, mc: MsgContent, live: Boolean = false, ttl: Int? = null): AChatItem? { val cmd = CC.ApiSendMessage(type, id, file, quotedItemId, mc, live, ttl) - val r = sendCmd(cmd, rhId) + val r = sendCmd(rh, cmd) return when (r) { is CR.NewChatItem -> r.chatItem else -> { @@ -639,8 +642,8 @@ object ChatController { } } - suspend fun apiGetChatItemInfo(type: ChatType, id: Long, itemId: Long): ChatItemInfo? { - return when (val r = sendCmd(CC.ApiGetChatItemInfo(type, id, itemId))) { + suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, itemId: Long): ChatItemInfo? { + return when (val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, itemId))) { is CR.ApiChatItemInfo -> r.chatItemInfo else -> { apiErrorAlert("apiGetChatItemInfo", generalGetString(MR.strings.error_loading_details), r) @@ -649,38 +652,38 @@ object ChatController { } } - suspend fun apiUpdateChatItem(type: ChatType, id: Long, itemId: Long, mc: MsgContent, live: Boolean = false): AChatItem? { - val r = sendCmd(CC.ApiUpdateChatItem(type, id, itemId, mc, live)) + suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, itemId: Long, mc: MsgContent, live: Boolean = false): AChatItem? { + val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, itemId, mc, live)) if (r is CR.ChatItemUpdated) return r.chatItem Log.e(TAG, "apiUpdateChatItem bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiChatItemReaction(type: ChatType, id: Long, itemId: Long, add: Boolean, reaction: MsgReaction): ChatItem? { - val r = sendCmd(CC.ApiChatItemReaction(type, id, itemId, add, reaction)) + suspend fun apiChatItemReaction(rh: Long?, type: ChatType, id: Long, itemId: Long, add: Boolean, reaction: MsgReaction): ChatItem? { + val r = sendCmd(rh, CC.ApiChatItemReaction(type, id, itemId, add, reaction)) if (r is CR.ChatItemReaction) return r.reaction.chatReaction.chatItem Log.e(TAG, "apiUpdateChatItem bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiDeleteChatItem(type: ChatType, id: Long, itemId: Long, mode: CIDeleteMode): CR.ChatItemDeleted? { - val r = sendCmd(CC.ApiDeleteChatItem(type, id, itemId, mode)) + suspend fun apiDeleteChatItem(rh: Long?, type: ChatType, id: Long, itemId: Long, mode: CIDeleteMode): CR.ChatItemDeleted? { + val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, itemId, mode)) if (r is CR.ChatItemDeleted) return r Log.e(TAG, "apiDeleteChatItem bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiDeleteMemberChatItem(groupId: Long, groupMemberId: Long, itemId: Long): Pair? { - val r = sendCmd(CC.ApiDeleteMemberChatItem(groupId, groupMemberId, itemId)) + suspend fun apiDeleteMemberChatItem(rh: Long?, groupId: Long, groupMemberId: Long, itemId: Long): Pair? { + val r = sendCmd(rh, CC.ApiDeleteMemberChatItem(groupId, groupMemberId, itemId)) if (r is CR.ChatItemDeleted) return r.deletedChatItem.chatItem to r.toChatItem?.chatItem Log.e(TAG, "apiDeleteMemberChatItem bad response: ${r.responseType} ${r.details}") return null } - suspend fun getUserProtoServers(serverProtocol: ServerProtocol): UserProtocolServers? { + suspend fun getUserProtoServers(rh: Long?, serverProtocol: ServerProtocol): UserProtocolServers? { val userId = kotlin.runCatching { currentUserId("getUserProtoServers") }.getOrElse { return null } - val r = sendCmd(CC.APIGetUserProtoServers(userId, serverProtocol)) - return if (r is CR.UserProtoServers) r.servers + val r = sendCmd(rh, CC.APIGetUserProtoServers(userId, serverProtocol)) + return if (r is CR.UserProtoServers) { if (rh == null) r.servers else r.servers.copy(protoServers = r.servers.protoServers.map { it.copy(remoteHostId = rh) }) } else { Log.e(TAG, "getUserProtoServers bad response: ${r.responseType} ${r.details}") AlertManager.shared.showAlertMsg( @@ -691,9 +694,9 @@ object ChatController { } } - suspend fun setUserProtoServers(serverProtocol: ServerProtocol, servers: List): Boolean { + suspend fun setUserProtoServers(rh: Long?, serverProtocol: ServerProtocol, servers: List): Boolean { val userId = kotlin.runCatching { currentUserId("setUserProtoServers") }.getOrElse { return false } - val r = sendCmd(CC.APISetUserProtoServers(userId, serverProtocol, servers)) + val r = sendCmd(rh, CC.APISetUserProtoServers(userId, serverProtocol, servers)) return when (r) { is CR.CmdOk -> true else -> { @@ -707,9 +710,9 @@ object ChatController { } } - suspend fun testProtoServer(server: String): ProtocolTestFailure? { + suspend fun testProtoServer(rh: Long?, server: String): ProtocolTestFailure? { val userId = currentUserId("testProtoServer") - val r = sendCmd(CC.APITestProtoServer(userId, server)) + val r = sendCmd(rh, CC.APITestProtoServer(userId, server)) return when (r) { is CR.ServerTestResult -> r.testFailure else -> { @@ -719,29 +722,22 @@ object ChatController { } } - suspend fun getChatItemTTL(): ChatItemTTL { + suspend fun getChatItemTTL(rh: Long?): ChatItemTTL { val userId = currentUserId("getChatItemTTL") - val r = sendCmd(CC.APIGetChatItemTTL(userId)) + val r = sendCmd(rh, CC.APIGetChatItemTTL(userId)) if (r is CR.ChatItemTTL) return ChatItemTTL.fromSeconds(r.chatItemTTL) throw Exception("failed to get chat item TTL: ${r.responseType} ${r.details}") } - suspend fun setChatItemTTL(chatItemTTL: ChatItemTTL) { + suspend fun setChatItemTTL(rh: Long?, chatItemTTL: ChatItemTTL) { val userId = currentUserId("setChatItemTTL") - val r = sendCmd(CC.APISetChatItemTTL(userId, chatItemTTL.seconds)) + val r = sendCmd(rh, CC.APISetChatItemTTL(userId, chatItemTTL.seconds)) if (r is CR.CmdOk) return throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}") } - suspend fun apiGetNetworkConfig(): NetCfg? { - val r = sendCmd(CC.APIGetNetworkConfig()) - if (r is CR.NetworkConfig) return r.networkConfig - Log.e(TAG, "apiGetNetworkConfig bad response: ${r.responseType} ${r.details}") - return null - } - suspend fun apiSetNetworkConfig(cfg: NetCfg): Boolean { - val r = sendCmd(CC.APISetNetworkConfig(cfg)) + val r = sendCmd(null, CC.APISetNetworkConfig(cfg)) return when (r) { is CR.CmdOk -> true else -> { @@ -755,8 +751,8 @@ object ChatController { } } - suspend fun apiSetSettings(type: ChatType, id: Long, settings: ChatSettings): Boolean { - val r = sendCmd(CC.APISetChatSettings(type, id, settings)) + suspend fun apiSetSettings(rh: Long?, type: ChatType, id: Long, settings: ChatSettings): Boolean { + val r = sendCmd(rh, CC.APISetChatSettings(type, id, settings)) return when (r) { is CR.CmdOk -> true else -> { @@ -766,88 +762,88 @@ object ChatController { } } - suspend fun apiSetMemberSettings(groupId: Long, groupMemberId: Long, memberSettings: GroupMemberSettings): Boolean = - sendCommandOkResp(CC.ApiSetMemberSettings(groupId, groupMemberId, memberSettings)) + suspend fun apiSetMemberSettings(rh: Long?, groupId: Long, groupMemberId: Long, memberSettings: GroupMemberSettings): Boolean = + sendCommandOkResp(rh, CC.ApiSetMemberSettings(groupId, groupMemberId, memberSettings)) - suspend fun apiContactInfo(contactId: Long): Pair? { - val r = sendCmd(CC.APIContactInfo(contactId)) + suspend fun apiContactInfo(rh: Long?, contactId: Long): Pair? { + val r = sendCmd(rh, CC.APIContactInfo(contactId)) if (r is CR.ContactInfo) return r.connectionStats_ to r.customUserProfile Log.e(TAG, "apiContactInfo bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiGroupMemberInfo(groupId: Long, groupMemberId: Long): Pair? { - val r = sendCmd(CC.APIGroupMemberInfo(groupId, groupMemberId)) + suspend fun apiGroupMemberInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { + val r = sendCmd(rh, CC.APIGroupMemberInfo(groupId, groupMemberId)) if (r is CR.GroupMemberInfo) return Pair(r.member, r.connectionStats_) Log.e(TAG, "apiGroupMemberInfo bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiSwitchContact(contactId: Long): ConnectionStats? { - val r = sendCmd(CC.APISwitchContact(contactId)) + suspend fun apiSwitchContact(rh: Long?, contactId: Long): ConnectionStats? { + val r = sendCmd(rh, CC.APISwitchContact(contactId)) if (r is CR.ContactSwitchStarted) return r.connectionStats apiErrorAlert("apiSwitchContact", generalGetString(MR.strings.error_changing_address), r) return null } - suspend fun apiSwitchGroupMember(groupId: Long, groupMemberId: Long): Pair? { - val r = sendCmd(CC.APISwitchGroupMember(groupId, groupMemberId)) + suspend fun apiSwitchGroupMember(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { + val r = sendCmd(rh, CC.APISwitchGroupMember(groupId, groupMemberId)) if (r is CR.GroupMemberSwitchStarted) return Pair(r.member, r.connectionStats) apiErrorAlert("apiSwitchGroupMember", generalGetString(MR.strings.error_changing_address), r) return null } - suspend fun apiAbortSwitchContact(contactId: Long): ConnectionStats? { - val r = sendCmd(CC.APIAbortSwitchContact(contactId)) + suspend fun apiAbortSwitchContact(rh: Long?, contactId: Long): ConnectionStats? { + val r = sendCmd(rh, CC.APIAbortSwitchContact(contactId)) if (r is CR.ContactSwitchAborted) return r.connectionStats apiErrorAlert("apiAbortSwitchContact", generalGetString(MR.strings.error_aborting_address_change), r) return null } - suspend fun apiAbortSwitchGroupMember(groupId: Long, groupMemberId: Long): Pair? { - val r = sendCmd(CC.APIAbortSwitchGroupMember(groupId, groupMemberId)) + suspend fun apiAbortSwitchGroupMember(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { + val r = sendCmd(rh, CC.APIAbortSwitchGroupMember(groupId, groupMemberId)) if (r is CR.GroupMemberSwitchAborted) return Pair(r.member, r.connectionStats) apiErrorAlert("apiAbortSwitchGroupMember", generalGetString(MR.strings.error_aborting_address_change), r) return null } - suspend fun apiSyncContactRatchet(contactId: Long, force: Boolean): ConnectionStats? { - val r = sendCmd(CC.APISyncContactRatchet(contactId, force)) + suspend fun apiSyncContactRatchet(rh: Long?, contactId: Long, force: Boolean): ConnectionStats? { + val r = sendCmd(rh, CC.APISyncContactRatchet(contactId, force)) if (r is CR.ContactRatchetSyncStarted) return r.connectionStats apiErrorAlert("apiSyncContactRatchet", generalGetString(MR.strings.error_synchronizing_connection), r) return null } - suspend fun apiSyncGroupMemberRatchet(groupId: Long, groupMemberId: Long, force: Boolean): Pair? { - val r = sendCmd(CC.APISyncGroupMemberRatchet(groupId, groupMemberId, force)) + suspend fun apiSyncGroupMemberRatchet(rh: Long?, groupId: Long, groupMemberId: Long, force: Boolean): Pair? { + val r = sendCmd(rh, CC.APISyncGroupMemberRatchet(groupId, groupMemberId, force)) if (r is CR.GroupMemberRatchetSyncStarted) return Pair(r.member, r.connectionStats) apiErrorAlert("apiSyncGroupMemberRatchet", generalGetString(MR.strings.error_synchronizing_connection), r) return null } - suspend fun apiGetContactCode(contactId: Long): Pair? { - val r = sendCmd(CC.APIGetContactCode(contactId)) + suspend fun apiGetContactCode(rh: Long?, contactId: Long): Pair? { + val r = sendCmd(rh, CC.APIGetContactCode(contactId)) if (r is CR.ContactCode) return r.contact to r.connectionCode Log.e(TAG,"failed to get contact code: ${r.responseType} ${r.details}") return null } - suspend fun apiGetGroupMemberCode(groupId: Long, groupMemberId: Long): Pair? { - val r = sendCmd(CC.APIGetGroupMemberCode(groupId, groupMemberId)) + suspend fun apiGetGroupMemberCode(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { + val r = sendCmd(rh, CC.APIGetGroupMemberCode(groupId, groupMemberId)) if (r is CR.GroupMemberCode) return r.member to r.connectionCode Log.e(TAG,"failed to get group member code: ${r.responseType} ${r.details}") return null } - suspend fun apiVerifyContact(contactId: Long, connectionCode: String?): Pair? { - return when (val r = sendCmd(CC.APIVerifyContact(contactId, connectionCode))) { + suspend fun apiVerifyContact(rh: Long?, contactId: Long, connectionCode: String?): Pair? { + return when (val r = sendCmd(rh, CC.APIVerifyContact(contactId, connectionCode))) { is CR.ConnectionVerified -> r.verified to r.expectedCode else -> null } } - suspend fun apiVerifyGroupMember(groupId: Long, groupMemberId: Long, connectionCode: String?): Pair? { - return when (val r = sendCmd(CC.APIVerifyGroupMember(groupId, groupMemberId, connectionCode))) { + suspend fun apiVerifyGroupMember(rh: Long?, groupId: Long, groupMemberId: Long, connectionCode: String?): Pair? { + return when (val r = sendCmd(rh, CC.APIVerifyGroupMember(groupId, groupMemberId, connectionCode))) { is CR.ConnectionVerified -> r.verified to r.expectedCode else -> null } @@ -855,12 +851,12 @@ object ChatController { - suspend fun apiAddContact(incognito: Boolean): Pair? { + suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair? { val userId = chatModel.currentUser.value?.userId ?: run { Log.e(TAG, "apiAddContact: no current user") return null } - val r = sendCmd(CC.APIAddContact(userId, incognito)) + val r = sendCmd(rh, CC.APIAddContact(userId, incognito)) return when (r) { is CR.Invitation -> r.connReqInvitation to r.connection else -> { @@ -872,27 +868,27 @@ object ChatController { } } - suspend fun apiSetConnectionIncognito(connId: Long, incognito: Boolean): PendingContactConnection? { - val r = sendCmd(CC.ApiSetConnectionIncognito(connId, incognito)) + suspend fun apiSetConnectionIncognito(rh: Long?, connId: Long, incognito: Boolean): PendingContactConnection? { + val r = sendCmd(rh, CC.ApiSetConnectionIncognito(connId, incognito)) if (r is CR.ConnectionIncognitoUpdated) return r.toConnection Log.e(TAG, "apiSetConnectionIncognito bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiConnectPlan(connReq: String): ConnectionPlan? { + suspend fun apiConnectPlan(rh: Long?, connReq: String): ConnectionPlan? { val userId = kotlin.runCatching { currentUserId("apiConnectPlan") }.getOrElse { return null } - val r = sendCmd(CC.APIConnectPlan(userId, connReq)) + val r = sendCmd(rh, CC.APIConnectPlan(userId, connReq)) if (r is CR.CRConnectionPlan) return r.connectionPlan Log.e(TAG, "apiConnectPlan bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiConnect(incognito: Boolean, connReq: String): Boolean { + suspend fun apiConnect(rh: Long?, incognito: Boolean, connReq: String): Boolean { val userId = chatModel.currentUser.value?.userId ?: run { Log.e(TAG, "apiConnect: no current user") return false } - val r = sendCmd(CC.APIConnect(userId, incognito, connReq)) + val r = sendCmd(rh, CC.APIConnect(userId, incognito, connReq)) when { r is CR.SentConfirmation || r is CR.SentInvitation -> return true r is CR.ContactAlreadyExists -> { @@ -928,12 +924,12 @@ object ChatController { } } - suspend fun apiConnectContactViaAddress(incognito: Boolean, contactId: Long): Contact? { + suspend fun apiConnectContactViaAddress(rh: Long?, incognito: Boolean, contactId: Long): Contact? { val userId = chatModel.currentUser.value?.userId ?: run { Log.e(TAG, "apiConnectContactViaAddress: no current user") return null } - val r = sendCmd(CC.ApiConnectContactViaAddress(userId, incognito, contactId)) + val r = sendCmd(rh, CC.ApiConnectContactViaAddress(userId, incognito, contactId)) when { r is CR.SentInvitationToContact -> return r.contact else -> { @@ -945,8 +941,8 @@ object ChatController { } } - suspend fun apiDeleteChat(type: ChatType, id: Long, notify: Boolean? = null): Boolean { - val r = sendCmd(CC.ApiDeleteChat(type, id, notify)) + suspend fun apiDeleteChat(rh: Long?, type: ChatType, id: Long, notify: Boolean? = null): Boolean { + val r = sendCmd(rh, CC.ApiDeleteChat(type, id, notify)) when { r is CR.ContactDeleted && type == ChatType.Direct -> return true r is CR.ContactConnectionDeleted && type == ChatType.ContactConnection -> return true @@ -964,24 +960,16 @@ object ChatController { return false } - suspend fun apiClearChat(type: ChatType, id: Long): ChatInfo? { - val r = sendCmd(CC.ApiClearChat(type, id)) + suspend fun apiClearChat(rh: Long?, type: ChatType, id: Long): ChatInfo? { + val r = sendCmd(rh, CC.ApiClearChat(type, id)) if (r is CR.ChatCleared) return r.chatInfo Log.e(TAG, "apiClearChat bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiListContacts(): List? { - val userId = kotlin.runCatching { currentUserId("apiListContacts") }.getOrElse { return null } - val r = sendCmd(CC.ApiListContacts(userId)) - if (r is CR.ContactsList) return r.contacts - Log.e(TAG, "apiListContacts bad response: ${r.responseType} ${r.details}") - return null - } - - suspend fun apiUpdateProfile(profile: Profile): Pair>? { + suspend fun apiUpdateProfile(rh: Long?, profile: Profile): Pair>? { val userId = kotlin.runCatching { currentUserId("apiUpdateProfile") }.getOrElse { return null } - val r = sendCmd(CC.ApiUpdateProfile(userId, profile)) + val r = sendCmd(rh, CC.ApiUpdateProfile(userId, profile)) if (r is CR.UserProfileNoChange) return profile to emptyList() if (r is CR.UserProfileUpdated) return r.toProfile to r.updateSummary.changedContacts if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.DuplicateName) { @@ -991,39 +979,39 @@ object ChatController { return null } - suspend fun apiSetProfileAddress(on: Boolean): User? { + suspend fun apiSetProfileAddress(rh: Long?, on: Boolean): User? { val userId = try { currentUserId("apiSetProfileAddress") } catch (e: Exception) { return null } - return when (val r = sendCmd(CC.ApiSetProfileAddress(userId, on))) { + return when (val r = sendCmd(rh, CC.ApiSetProfileAddress(userId, on))) { is CR.UserProfileNoChange -> null is CR.UserProfileUpdated -> r.user else -> throw Exception("failed to set profile address: ${r.responseType} ${r.details}") } } - suspend fun apiSetContactPrefs(contactId: Long, prefs: ChatPreferences): Contact? { - val r = sendCmd(CC.ApiSetContactPrefs(contactId, prefs)) + suspend fun apiSetContactPrefs(rh: Long?, contactId: Long, prefs: ChatPreferences): Contact? { + val r = sendCmd(rh, CC.ApiSetContactPrefs(contactId, prefs)) if (r is CR.ContactPrefsUpdated) return r.toContact Log.e(TAG, "apiSetContactPrefs bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiSetContactAlias(contactId: Long, localAlias: String): Contact? { - val r = sendCmd(CC.ApiSetContactAlias(contactId, localAlias)) + suspend fun apiSetContactAlias(rh: Long?, contactId: Long, localAlias: String): Contact? { + val r = sendCmd(rh, CC.ApiSetContactAlias(contactId, localAlias)) if (r is CR.ContactAliasUpdated) return r.toContact Log.e(TAG, "apiSetContactAlias bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiSetConnectionAlias(connId: Long, localAlias: String): PendingContactConnection? { - val r = sendCmd(CC.ApiSetConnectionAlias(connId, localAlias)) + suspend fun apiSetConnectionAlias(rh: Long?, connId: Long, localAlias: String): PendingContactConnection? { + val r = sendCmd(rh, CC.ApiSetConnectionAlias(connId, localAlias)) if (r is CR.ConnectionAliasUpdated) return r.toConnection Log.e(TAG, "apiSetConnectionAlias bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiCreateUserAddress(): String? { + suspend fun apiCreateUserAddress(rh: Long?): String? { val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null } - val r = sendCmd(CC.ApiCreateMyAddress(userId)) + val r = sendCmd(rh, CC.ApiCreateMyAddress(userId)) return when (r) { is CR.UserContactLinkCreated -> r.connReqContact else -> { @@ -1035,17 +1023,17 @@ object ChatController { } } - suspend fun apiDeleteUserAddress(): User? { + suspend fun apiDeleteUserAddress(rh: Long?): User? { val userId = try { currentUserId("apiDeleteUserAddress") } catch (e: Exception) { return null } - val r = sendCmd(CC.ApiDeleteMyAddress(userId)) + val r = sendCmd(rh, CC.ApiDeleteMyAddress(userId)) if (r is CR.UserContactLinkDeleted) return r.user Log.e(TAG, "apiDeleteUserAddress bad response: ${r.responseType} ${r.details}") return null } - private suspend fun apiGetUserAddress(): UserContactLinkRec? { + private suspend fun apiGetUserAddress(rh: Long?): UserContactLinkRec? { val userId = kotlin.runCatching { currentUserId("apiGetUserAddress") }.getOrElse { return null } - val r = sendCmd(CC.ApiShowMyAddress(userId)) + val r = sendCmd(rh, CC.ApiShowMyAddress(userId)) if (r is CR.UserContactLink) return r.contactLink if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.UserContactLinkNotFound @@ -1056,9 +1044,9 @@ object ChatController { return null } - suspend fun userAddressAutoAccept(autoAccept: AutoAccept?): UserContactLinkRec? { + suspend fun userAddressAutoAccept(rh: Long?, autoAccept: AutoAccept?): UserContactLinkRec? { val userId = kotlin.runCatching { currentUserId("userAddressAutoAccept") }.getOrElse { return null } - val r = sendCmd(CC.ApiAddressAutoAccept(userId, autoAccept)) + val r = sendCmd(rh, CC.ApiAddressAutoAccept(userId, autoAccept)) if (r is CR.UserContactLinkUpdated) return r.contactLink if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.UserContactLinkNotFound @@ -1069,8 +1057,8 @@ object ChatController { return null } - suspend fun apiAcceptContactRequest(incognito: Boolean, contactReqId: Long): Contact? { - val r = sendCmd(CC.ApiAcceptContact(incognito, contactReqId)) + suspend fun apiAcceptContactRequest(rh: Long?, incognito: Boolean, contactReqId: Long): Contact? { + val r = sendCmd(rh, CC.ApiAcceptContact(incognito, contactReqId)) return when { r is CR.AcceptingContactRequest -> r.contact r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent @@ -1091,76 +1079,76 @@ object ChatController { } } - suspend fun apiRejectContactRequest(contactReqId: Long): Boolean { - val r = sendCmd(CC.ApiRejectContact(contactReqId)) + suspend fun apiRejectContactRequest(rh: Long?, contactReqId: Long): Boolean { + val r = sendCmd(rh, CC.ApiRejectContact(contactReqId)) if (r is CR.ContactRequestRejected) return true Log.e(TAG, "apiRejectContactRequest bad response: ${r.responseType} ${r.details}") return false } - suspend fun apiSendCallInvitation(contact: Contact, callType: CallType): Boolean { - val r = sendCmd(CC.ApiSendCallInvitation(contact, callType)) + suspend fun apiSendCallInvitation(rh: Long?, contact: Contact, callType: CallType): Boolean { + val r = sendCmd(rh, CC.ApiSendCallInvitation(contact, callType)) return r is CR.CmdOk } - suspend fun apiRejectCall(contact: Contact): Boolean { - val r = sendCmd(CC.ApiRejectCall(contact)) + suspend fun apiRejectCall(rh: Long?, contact: Contact): Boolean { + val r = sendCmd(rh, CC.ApiRejectCall(contact)) return r is CR.CmdOk } - suspend fun apiSendCallOffer(contact: Contact, rtcSession: String, rtcIceCandidates: String, media: CallMediaType, capabilities: CallCapabilities): Boolean { + suspend fun apiSendCallOffer(rh: Long?, contact: Contact, rtcSession: String, rtcIceCandidates: String, media: CallMediaType, capabilities: CallCapabilities): Boolean { val webRtcSession = WebRTCSession(rtcSession, rtcIceCandidates) val callOffer = WebRTCCallOffer(CallType(media, capabilities), webRtcSession) - val r = sendCmd(CC.ApiSendCallOffer(contact, callOffer)) + val r = sendCmd(rh, CC.ApiSendCallOffer(contact, callOffer)) return r is CR.CmdOk } - suspend fun apiSendCallAnswer(contact: Contact, rtcSession: String, rtcIceCandidates: String): Boolean { + suspend fun apiSendCallAnswer(rh: Long?, contact: Contact, rtcSession: String, rtcIceCandidates: String): Boolean { val answer = WebRTCSession(rtcSession, rtcIceCandidates) - val r = sendCmd(CC.ApiSendCallAnswer(contact, answer)) + val r = sendCmd(rh, CC.ApiSendCallAnswer(contact, answer)) return r is CR.CmdOk } - suspend fun apiSendCallExtraInfo(contact: Contact, rtcIceCandidates: String): Boolean { + suspend fun apiSendCallExtraInfo(rh: Long?, contact: Contact, rtcIceCandidates: String): Boolean { val extraInfo = WebRTCExtraInfo(rtcIceCandidates) - val r = sendCmd(CC.ApiSendCallExtraInfo(contact, extraInfo)) + val r = sendCmd(rh, CC.ApiSendCallExtraInfo(contact, extraInfo)) return r is CR.CmdOk } - suspend fun apiEndCall(contact: Contact): Boolean { - val r = sendCmd(CC.ApiEndCall(contact)) + suspend fun apiEndCall(rh: Long?, contact: Contact): Boolean { + val r = sendCmd(rh, CC.ApiEndCall(contact)) return r is CR.CmdOk } - suspend fun apiCallStatus(contact: Contact, status: WebRTCCallStatus): Boolean { - val r = sendCmd(CC.ApiCallStatus(contact, status)) + suspend fun apiCallStatus(rh: Long?, contact: Contact, status: WebRTCCallStatus): Boolean { + val r = sendCmd(rh, CC.ApiCallStatus(contact, status)) return r is CR.CmdOk } - suspend fun apiGetNetworkStatuses(): List? { - val r = sendCmd(CC.ApiGetNetworkStatuses()) + suspend fun apiGetNetworkStatuses(rh: Long?): List? { + val r = sendCmd(rh, CC.ApiGetNetworkStatuses()) if (r is CR.NetworkStatuses) return r.networkStatuses Log.e(TAG, "apiGetNetworkStatuses bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiChatRead(type: ChatType, id: Long, range: CC.ItemRange): Boolean { - val r = sendCmd(CC.ApiChatRead(type, id, range)) + suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long, range: CC.ItemRange): Boolean { + val r = sendCmd(rh, CC.ApiChatRead(type, id, range)) if (r is CR.CmdOk) return true Log.e(TAG, "apiChatRead bad response: ${r.responseType} ${r.details}") return false } - suspend fun apiChatUnread(type: ChatType, id: Long, unreadChat: Boolean): Boolean { - val r = sendCmd(CC.ApiChatUnread(type, id, unreadChat)) + suspend fun apiChatUnread(rh: Long?, type: ChatType, id: Long, unreadChat: Boolean): Boolean { + val r = sendCmd(rh, CC.ApiChatUnread(type, id, unreadChat)) if (r is CR.CmdOk) return true Log.e(TAG, "apiChatUnread bad response: ${r.responseType} ${r.details}") return false } - suspend fun apiReceiveFile(rhId: Long?, fileId: Long, encrypted: Boolean, inline: Boolean? = null, auto: Boolean = false): AChatItem? { + suspend fun apiReceiveFile(rh: Long?, fileId: Long, encrypted: Boolean, inline: Boolean? = null, auto: Boolean = false): AChatItem? { // -1 here is to override default behavior of providing current remote host id because file can be asked by local device while remote is connected - val r = sendCmd(CC.ReceiveFile(fileId, encrypted, inline), rhId ?: -1) + val r = sendCmd(rh, CC.ReceiveFile(fileId, encrypted, inline)) return when (r) { is CR.RcvFileAccepted -> r.chatItem is CR.RcvFileAcceptedSndCancelled -> { @@ -1188,16 +1176,16 @@ object ChatController { } } - suspend fun cancelFile(rhId: Long?, user: User, fileId: Long) { - val chatItem = apiCancelFile(fileId) + suspend fun cancelFile(rh: Long?, user: User, fileId: Long) { + val chatItem = apiCancelFile(rh, fileId) if (chatItem != null) { - chatItemSimpleUpdate(rhId, user, chatItem) + chatItemSimpleUpdate(rh, user, chatItem) cleanupFile(chatItem) } } - suspend fun apiCancelFile(fileId: Long): AChatItem? { - val r = sendCmd(CC.CancelFile(fileId)) + suspend fun apiCancelFile(rh: Long?, fileId: Long): AChatItem? { + val r = sendCmd(rh, CC.CancelFile(fileId)) return when (r) { is CR.SndFileCancelled -> r.chatItem is CR.RcvFileCancelled -> r.chatItem @@ -1208,16 +1196,16 @@ object ChatController { } } - suspend fun apiNewGroup(incognito: Boolean, groupProfile: GroupProfile): GroupInfo? { + suspend fun apiNewGroup(rh: Long?, incognito: Boolean, groupProfile: GroupProfile): GroupInfo? { val userId = kotlin.runCatching { currentUserId("apiNewGroup") }.getOrElse { return null } - val r = sendCmd(CC.ApiNewGroup(userId, incognito, groupProfile)) + val r = sendCmd(rh, CC.ApiNewGroup(userId, incognito, groupProfile)) if (r is CR.GroupCreated) return r.groupInfo Log.e(TAG, "apiNewGroup bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiAddMember(groupId: Long, contactId: Long, memberRole: GroupMemberRole): GroupMember? { - val r = sendCmd(CC.ApiAddMember(groupId, contactId, memberRole)) + suspend fun apiAddMember(rh: Long?, groupId: Long, contactId: Long, memberRole: GroupMemberRole): GroupMember? { + val r = sendCmd(rh, CC.ApiAddMember(groupId, contactId, memberRole)) return when (r) { is CR.SentGroupInvitation -> r.member else -> { @@ -1229,14 +1217,14 @@ object ChatController { } } - suspend fun apiJoinGroup(groupId: Long) { - val r = sendCmd(CC.ApiJoinGroup(groupId)) + suspend fun apiJoinGroup(rh: Long?, groupId: Long) { + val r = sendCmd(rh, CC.ApiJoinGroup(groupId)) when (r) { is CR.UserAcceptedGroupSent -> - chatModel.updateGroup(r.groupInfo) + chatModel.updateGroup(rh, r.groupInfo) is CR.ChatCmdError -> { val e = r.chatError - suspend fun deleteGroup() { if (apiDeleteChat(ChatType.Group, groupId)) { chatModel.removeChat("#$groupId") } } + suspend fun deleteGroup() { if (apiDeleteChat(rh, ChatType.Group, groupId)) { chatModel.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)) @@ -1251,8 +1239,8 @@ object ChatController { } } - suspend fun apiRemoveMember(groupId: Long, memberId: Long): GroupMember? = - when (val r = sendCmd(CC.ApiRemoveMember(groupId, memberId))) { + suspend fun apiRemoveMember(rh: Long?, groupId: Long, memberId: Long): GroupMember? = + when (val r = sendCmd(rh, CC.ApiRemoveMember(groupId, memberId))) { is CR.UserDeletedMember -> r.member else -> { if (!(networkErrorAlert(r))) { @@ -1262,8 +1250,8 @@ object ChatController { } } - suspend fun apiMemberRole(groupId: Long, memberId: Long, memberRole: GroupMemberRole): GroupMember = - when (val r = sendCmd(CC.ApiMemberRole(groupId, memberId, memberRole))) { + suspend fun apiMemberRole(rh: Long?, groupId: Long, memberId: Long, memberRole: GroupMemberRole): GroupMember = + when (val r = sendCmd(rh, CC.ApiMemberRole(groupId, memberId, memberRole))) { is CR.MemberRoleUser -> r.member else -> { if (!(networkErrorAlert(r))) { @@ -1273,22 +1261,22 @@ object ChatController { } } - suspend fun apiLeaveGroup(groupId: Long): GroupInfo? { - val r = sendCmd(CC.ApiLeaveGroup(groupId)) + suspend fun apiLeaveGroup(rh: Long?, groupId: Long): GroupInfo? { + val r = sendCmd(rh, CC.ApiLeaveGroup(groupId)) if (r is CR.LeftMemberUser) return r.groupInfo Log.e(TAG, "apiLeaveGroup bad response: ${r.responseType} ${r.details}") return null } - suspend fun apiListMembers(groupId: Long): List { - val r = sendCmd(CC.ApiListMembers(groupId)) + suspend fun apiListMembers(rh: Long?, groupId: Long): List { + val r = sendCmd(rh, CC.ApiListMembers(groupId)) if (r is CR.GroupMembers) return r.group.members Log.e(TAG, "apiListMembers bad response: ${r.responseType} ${r.details}") return emptyList() } - suspend fun apiUpdateGroup(groupId: Long, groupProfile: GroupProfile): GroupInfo? { - return when (val r = sendCmd(CC.ApiUpdateGroupProfile(groupId, groupProfile))) { + suspend fun apiUpdateGroup(rh: Long?, groupId: Long, groupProfile: GroupProfile): GroupInfo? { + return when (val r = sendCmd(rh, CC.ApiUpdateGroupProfile(groupId, groupProfile))) { is CR.GroupUpdated -> r.toGroup is CR.ChatCmdError -> { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_saving_group_profile), "$r.chatError") @@ -1305,8 +1293,8 @@ object ChatController { } } - suspend fun apiCreateGroupLink(groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { - return when (val r = sendCmd(CC.APICreateGroupLink(groupId, memberRole))) { + suspend fun apiCreateGroupLink(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { + return when (val r = sendCmd(rh, CC.APICreateGroupLink(groupId, memberRole))) { is CR.GroupLinkCreated -> r.connReqContact to r.memberRole else -> { if (!(networkErrorAlert(r))) { @@ -1317,8 +1305,8 @@ object ChatController { } } - suspend fun apiGroupLinkMemberRole(groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { - return when (val r = sendCmd(CC.APIGroupLinkMemberRole(groupId, memberRole))) { + suspend fun apiGroupLinkMemberRole(rh: Long?, groupId: Long, memberRole: GroupMemberRole = GroupMemberRole.Member): Pair? { + return when (val r = sendCmd(rh, CC.APIGroupLinkMemberRole(groupId, memberRole))) { is CR.GroupLink -> r.connReqContact to r.memberRole else -> { if (!(networkErrorAlert(r))) { @@ -1329,8 +1317,8 @@ object ChatController { } } - suspend fun apiDeleteGroupLink(groupId: Long): Boolean { - return when (val r = sendCmd(CC.APIDeleteGroupLink(groupId))) { + suspend fun apiDeleteGroupLink(rh: Long?, groupId: Long): Boolean { + return when (val r = sendCmd(rh, CC.APIDeleteGroupLink(groupId))) { is CR.GroupLinkDeleted -> true else -> { if (!(networkErrorAlert(r))) { @@ -1341,8 +1329,8 @@ object ChatController { } } - suspend fun apiGetGroupLink(groupId: Long): Pair? { - return when (val r = sendCmd(CC.APIGetGroupLink(groupId))) { + suspend fun apiGetGroupLink(rh: Long?, groupId: Long): Pair? { + return when (val r = sendCmd(rh, CC.APIGetGroupLink(groupId))) { is CR.GroupLink -> r.connReqContact to r.memberRole else -> { Log.e(TAG, "apiGetGroupLink bad response: ${r.responseType} ${r.details}") @@ -1351,8 +1339,8 @@ object ChatController { } } - suspend fun apiCreateMemberContact(groupId: Long, groupMemberId: Long): Contact? { - return when (val r = sendCmd(CC.APICreateMemberContact(groupId, groupMemberId))) { + suspend fun apiCreateMemberContact(rh: Long?, groupId: Long, groupMemberId: Long): Contact? { + return when (val r = sendCmd(rh, CC.APICreateMemberContact(groupId, groupMemberId))) { is CR.NewMemberContact -> r.contact else -> { if (!(networkErrorAlert(r))) { @@ -1363,8 +1351,8 @@ object ChatController { } } - suspend fun apiSendMemberContactInvitation(contactId: Long, mc: MsgContent): Contact? { - return when (val r = sendCmd(CC.APISendMemberContactInvitation(contactId, mc))) { + suspend fun apiSendMemberContactInvitation(rh: Long?, contactId: Long, mc: MsgContent): Contact? { + return when (val r = sendCmd(rh, CC.APISendMemberContactInvitation(contactId, mc))) { is CR.NewMemberContactSentInv -> r.contact else -> { if (!(networkErrorAlert(r))) { @@ -1375,18 +1363,18 @@ object ChatController { } } - suspend fun allowFeatureToContact(contact: Contact, feature: ChatFeature, param: Int? = null) { + suspend fun allowFeatureToContact(rh: Long?, contact: Contact, feature: ChatFeature, param: Int? = null) { val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature, param = param) - val toContact = apiSetContactPrefs(contact.contactId, prefs) + val toContact = apiSetContactPrefs(rh, contact.contactId, prefs) if (toContact != null) { - chatModel.updateContact(toContact) + chatModel.updateContact(rh, toContact) } } - suspend fun setLocalDeviceName(displayName: String): Boolean = sendCommandOkResp(CC.SetLocalDeviceName(displayName)) + suspend fun setLocalDeviceName(displayName: String): Boolean = sendCommandOkResp(null, CC.SetLocalDeviceName(displayName)) suspend fun listRemoteHosts(): List? { - val r = sendCmd(CC.ListRemoteHosts()) + val r = sendCmd(null, CC.ListRemoteHosts()) if (r is CR.RemoteHostList) return r.remoteHosts apiErrorAlert("listRemoteHosts", generalGetString(MR.strings.error_alert_title), r) return null @@ -1399,20 +1387,20 @@ object ChatController { } suspend fun startRemoteHost(rhId: Long?, multicast: Boolean = false): Pair? { - val r = sendCmd(CC.StartRemoteHost(rhId, multicast)) + val r = sendCmd(null, CC.StartRemoteHost(rhId, multicast)) if (r is CR.RemoteHostStarted) return r.remoteHost_ to r.invitation apiErrorAlert("listRemoteHosts", generalGetString(MR.strings.error_alert_title), r) return null } suspend fun switchRemoteHost (rhId: Long?): RemoteHostInfo? { - val r = sendCmd(CC.SwitchRemoteHost(rhId)) + val r = sendCmd(null, CC.SwitchRemoteHost(rhId)) if (r is CR.CurrentRemoteHost) return r.remoteHost_ apiErrorAlert("switchRemoteHost", generalGetString(MR.strings.error_alert_title), r) return null } - suspend fun stopRemoteHost(rhId: Long?): Boolean = sendCommandOkResp(CC.StopRemoteHost(rhId)) + suspend fun stopRemoteHost(rhId: Long?): Boolean = sendCommandOkResp(null, CC.StopRemoteHost(rhId)) fun stopRemoteHostAndReloadHosts(h: RemoteHostInfo, switchToLocal: Boolean) { withBGApi { @@ -1425,55 +1413,55 @@ object ChatController { } } - suspend fun deleteRemoteHost(rhId: Long): Boolean = sendCommandOkResp(CC.DeleteRemoteHost(rhId)) + suspend fun deleteRemoteHost(rhId: Long): Boolean = sendCommandOkResp(null, CC.DeleteRemoteHost(rhId)) suspend fun storeRemoteFile(rhId: Long, storeEncrypted: Boolean?, localPath: String): CryptoFile? { - val r = sendCmd(CC.StoreRemoteFile(rhId, storeEncrypted, localPath)) + val r = sendCmd(null, CC.StoreRemoteFile(rhId, storeEncrypted, localPath)) if (r is CR.RemoteFileStored) return r.remoteFileSource apiErrorAlert("storeRemoteFile", generalGetString(MR.strings.error_alert_title), r) return null } - suspend fun getRemoteFile(rhId: Long, file: RemoteFile): Boolean = sendCommandOkResp(CC.GetRemoteFile(rhId, file)) + suspend fun getRemoteFile(rhId: Long, file: RemoteFile): Boolean = sendCommandOkResp(null, CC.GetRemoteFile(rhId, file)) suspend fun connectRemoteCtrl(desktopAddress: String): Pair { - val r = sendCmd(CC.ConnectRemoteCtrl(desktopAddress)) + val r = sendCmd(null, CC.ConnectRemoteCtrl(desktopAddress)) if (r is CR.RemoteCtrlConnecting) return SomeRemoteCtrl(r.remoteCtrl_, r.ctrlAppInfo, r.appVersion) to null else if (r is CR.ChatCmdError) return null to r else throw Exception("connectRemoteCtrl error: ${r.responseType} ${r.details}") } - suspend fun findKnownRemoteCtrl(): Boolean = sendCommandOkResp(CC.FindKnownRemoteCtrl()) + suspend fun findKnownRemoteCtrl(): Boolean = sendCommandOkResp(null, CC.FindKnownRemoteCtrl()) - suspend fun confirmRemoteCtrl(rcId: Long): Boolean = sendCommandOkResp(CC.ConfirmRemoteCtrl(rcId)) + suspend fun confirmRemoteCtrl(rcId: Long): Boolean = sendCommandOkResp(null, CC.ConfirmRemoteCtrl(rcId)) suspend fun verifyRemoteCtrlSession(sessionCode: String): RemoteCtrlInfo? { - val r = sendCmd(CC.VerifyRemoteCtrlSession(sessionCode)) + val r = sendCmd(null, CC.VerifyRemoteCtrlSession(sessionCode)) if (r is CR.RemoteCtrlConnected) return r.remoteCtrl apiErrorAlert("verifyRemoteCtrlSession", generalGetString(MR.strings.error_alert_title), r) return null } suspend fun listRemoteCtrls(): List? { - val r = sendCmd(CC.ListRemoteCtrls()) + val r = sendCmd(null, CC.ListRemoteCtrls()) if (r is CR.RemoteCtrlList) return r.remoteCtrls apiErrorAlert("listRemoteCtrls", generalGetString(MR.strings.error_alert_title), r) return null } - suspend fun stopRemoteCtrl(): Boolean = sendCommandOkResp(CC.StopRemoteCtrl()) + suspend fun stopRemoteCtrl(): Boolean = sendCommandOkResp(null, CC.StopRemoteCtrl()) - suspend fun deleteRemoteCtrl(rcId: Long): Boolean = sendCommandOkResp(CC.DeleteRemoteCtrl(rcId)) + suspend fun deleteRemoteCtrl(rcId: Long): Boolean = sendCommandOkResp(null, CC.DeleteRemoteCtrl(rcId)) - private suspend fun sendCommandOkResp(cmd: CC): Boolean { - val r = sendCmd(cmd) + private suspend fun sendCommandOkResp(rh: Long?, cmd: CC): Boolean { + val r = sendCmd(rh, cmd) val ok = r is CR.CmdOk if (!ok) apiErrorAlert(cmd.cmdType, generalGetString(MR.strings.error_alert_title), r) return ok } suspend fun apiGetVersion(): CoreVersionInfo? { - val r = sendCmd(CC.ShowVersion()) + val r = sendCmd(null, CC.ShowVersion()) return if (r is CR.VersionInfo) { r.versionInfo } else { @@ -1517,30 +1505,30 @@ object ChatController { val r = apiResp.resp val rhId = apiResp.remoteHostId fun active(user: UserLike): Boolean = activeUser(rhId, user) - chatModel.addTerminalItem(TerminalItem.resp(r)) + chatModel.addTerminalItem(TerminalItem.resp(rhId, r)) when (r) { is CR.NewContactConnection -> { if (active(r.user)) { - chatModel.updateContactConnection(r.connection) + chatModel.updateContactConnection(rhId, r.connection) } } is CR.ContactConnectionDeleted -> { if (active(r.user)) { - chatModel.removeChat(r.connection.id) + chatModel.removeChat(rhId, r.connection.id) } } is CR.ContactDeletedByContact -> { if (active(r.user) && r.contact.directOrUsed) { - chatModel.updateContact(r.contact) + chatModel.updateContact(rhId, r.contact) } } is CR.ContactConnected -> { if (active(r.user) && r.contact.directOrUsed) { - chatModel.updateContact(r.contact) + chatModel.updateContact(rhId, r.contact) val conn = r.contact.activeConn if (conn != null) { chatModel.dismissConnReqView(conn.id) - chatModel.removeChat(conn.id) + chatModel.removeChat(rhId, conn.id) } } if (r.contact.directOrUsed) { @@ -1550,11 +1538,11 @@ object ChatController { } is CR.ContactConnecting -> { if (active(r.user) && r.contact.directOrUsed) { - chatModel.updateContact(r.contact) + chatModel.updateContact(rhId, r.contact) val conn = r.contact.activeConn if (conn != null) { chatModel.dismissConnReqView(conn.id) - chatModel.removeChat(conn.id) + chatModel.removeChat(rhId, conn.id) } } } @@ -1562,31 +1550,31 @@ object ChatController { val contactRequest = r.contactRequest val cInfo = ChatInfo.ContactRequest(contactRequest) if (active(r.user)) { - if (chatModel.hasChat(contactRequest.id)) { - chatModel.updateChatInfo(cInfo) + if (chatModel.hasChat(rhId, contactRequest.id)) { + chatModel.updateChatInfo(rhId, cInfo) } else { - chatModel.addChat(Chat(chatInfo = cInfo, chatItems = listOf())) + chatModel.addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = listOf())) } } ntfManager.notifyContactRequestReceived(r.user, cInfo) } is CR.ContactUpdated -> { - if (active(r.user) && chatModel.hasChat(r.toContact.id)) { + if (active(r.user) && chatModel.hasChat(rhId, r.toContact.id)) { val cInfo = ChatInfo.Direct(r.toContact) - chatModel.updateChatInfo(cInfo) + chatModel.updateChatInfo(rhId, cInfo) } } is CR.GroupMemberUpdated -> { if (active(r.user)) { - chatModel.upsertGroupMember(r.groupInfo, r.toMember) + chatModel.upsertGroupMember(rhId, r.groupInfo, r.toMember) } } is CR.ContactsMerged -> { - if (active(r.user) && chatModel.hasChat(r.mergedContact.id)) { + if (active(r.user) && chatModel.hasChat(rhId, r.mergedContact.id)) { if (chatModel.chatId.value == r.mergedContact.id) { chatModel.chatId.value = r.intoContact.id } - chatModel.removeChat(r.mergedContact.id) + chatModel.removeChat(rhId, r.mergedContact.id) } } is CR.ContactsSubscribed -> updateContactsStatus(r.contactRefs, NetworkStatus.Connected()) @@ -1594,7 +1582,7 @@ object ChatController { is CR.ContactSubSummary -> { for (sub in r.contactSubscriptions) { if (active(r.user)) { - chatModel.updateContact(sub.contact) + chatModel.updateContact(rhId, sub.contact) } val err = sub.contactError if (err == null) { @@ -1618,9 +1606,9 @@ object ChatController { val cInfo = r.chatItem.chatInfo val cItem = r.chatItem.chatItem if (active(r.user)) { - chatModel.addChatItem(cInfo, cItem) + chatModel.addChatItem(rhId, cInfo, cItem) } else if (cItem.isRcvNew && cInfo.ntfsEnabled) { - chatModel.increaseUnreadCounter(r.user) + chatModel.increaseUnreadCounter(rhId, r.user) } val file = cItem.file val mc = cItem.content.msgContent @@ -1631,7 +1619,7 @@ object ChatController { || (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted))) { withApi { receiveFile(rhId, r.user, file.fileId, encrypted = cItem.encryptLocalFile && chatController.appPrefs.privacyEncryptLocalFiles.get(), auto = true) } } - if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id)) { + if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id || chatModel.remoteHostId != rhId)) { ntfManager.notifyMessageReceived(r.user, cInfo, cItem) } } @@ -1652,7 +1640,7 @@ object ChatController { is CR.ChatItemDeleted -> { if (!active(r.user)) { if (r.toChatItem == null && r.deletedChatItem.chatItem.isRcvNew && r.deletedChatItem.chatInfo.ntfsEnabled) { - chatModel.decreaseUnreadCounter(r.user) + chatModel.decreaseUnreadCounter(rhId, r.user) } return } @@ -1671,76 +1659,76 @@ object ChatController { ) } if (r.toChatItem == null) { - chatModel.removeChatItem(cInfo, cItem) + chatModel.removeChatItem(rhId, cInfo, cItem) } else { - chatModel.upsertChatItem(cInfo, r.toChatItem.chatItem) + chatModel.upsertChatItem(rhId, cInfo, r.toChatItem.chatItem) } } is CR.ReceivedGroupInvitation -> { if (active(r.user)) { - chatModel.updateGroup(r.groupInfo) // update so that repeat group invitations are not duplicated + chatModel.updateGroup(rhId, r.groupInfo) // update so that repeat group invitations are not duplicated // TODO NtfManager.shared.notifyGroupInvitation } } is CR.UserAcceptedGroupSent -> { if (!active(r.user)) return - chatModel.updateGroup(r.groupInfo) + chatModel.updateGroup(rhId, r.groupInfo) val conn = r.hostContact?.activeConn if (conn != null) { chatModel.dismissConnReqView(conn.id) - chatModel.removeChat(conn.id) + chatModel.removeChat(rhId, conn.id) } } is CR.GroupLinkConnecting -> { if (!active(r.user)) return - chatModel.updateGroup(r.groupInfo) + chatModel.updateGroup(rhId, r.groupInfo) val hostConn = r.hostMember.activeConn if (hostConn != null) { chatModel.dismissConnReqView(hostConn.id) - chatModel.removeChat(hostConn.id) + chatModel.removeChat(rhId, hostConn.id) } } is CR.JoinedGroupMemberConnecting -> if (active(r.user)) { - chatModel.upsertGroupMember(r.groupInfo, r.member) + chatModel.upsertGroupMember(rhId, r.groupInfo, r.member) } is CR.DeletedMemberUser -> // TODO update user member if (active(r.user)) { - chatModel.updateGroup(r.groupInfo) + chatModel.updateGroup(rhId, r.groupInfo) } is CR.DeletedMember -> if (active(r.user)) { - chatModel.upsertGroupMember(r.groupInfo, r.deletedMember) + chatModel.upsertGroupMember(rhId, r.groupInfo, r.deletedMember) } is CR.LeftMember -> if (active(r.user)) { - chatModel.upsertGroupMember(r.groupInfo, r.member) + chatModel.upsertGroupMember(rhId, r.groupInfo, r.member) } is CR.MemberRole -> if (active(r.user)) { - chatModel.upsertGroupMember(r.groupInfo, r.member) + chatModel.upsertGroupMember(rhId, r.groupInfo, r.member) } is CR.MemberRoleUser -> if (active(r.user)) { - chatModel.upsertGroupMember(r.groupInfo, r.member) + chatModel.upsertGroupMember(rhId, r.groupInfo, r.member) } is CR.GroupDeleted -> // TODO update user member if (active(r.user)) { - chatModel.updateGroup(r.groupInfo) + chatModel.updateGroup(rhId, r.groupInfo) } is CR.UserJoinedGroup -> if (active(r.user)) { - chatModel.updateGroup(r.groupInfo) + chatModel.updateGroup(rhId, r.groupInfo) } is CR.JoinedGroupMember -> if (active(r.user)) { - chatModel.upsertGroupMember(r.groupInfo, r.member) + chatModel.upsertGroupMember(rhId, r.groupInfo, r.member) } is CR.ConnectedToGroupMember -> { if (active(r.user)) { - chatModel.upsertGroupMember(r.groupInfo, r.member) + chatModel.upsertGroupMember(rhId, r.groupInfo, r.member) } if (r.memberContact != null) { chatModel.setContactNetworkStatus(r.memberContact, NetworkStatus.Connected()) @@ -1748,11 +1736,11 @@ object ChatController { } is CR.GroupUpdated -> if (active(r.user)) { - chatModel.updateGroup(r.toGroup) + chatModel.updateGroup(rhId, r.toGroup) } is CR.NewMemberContactReceivedInv -> if (active(r.user)) { - chatModel.updateContact(r.contact) + chatModel.updateContact(rhId, r.contact) } is CR.RcvFileStart -> chatItemSimpleUpdate(rhId, r.user, r.chatItem) @@ -1789,7 +1777,7 @@ object ChatController { cleanupFile(r.chatItem) } is CR.CallInvitation -> { - chatModel.callManager.reportNewIncomingCall(r.callInvitation) + chatModel.callManager.reportNewIncomingCall(r.callInvitation.copy(remoteHostId = rhId)) } is CR.CallOffer -> { // TODO askConfirmation? @@ -1834,13 +1822,13 @@ object ChatController { } } is CR.ContactSwitch -> - chatModel.updateContactConnectionStats(r.contact, r.switchProgress.connectionStats) + chatModel.updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats) is CR.GroupMemberSwitch -> - chatModel.updateGroupMemberConnectionStats(r.groupInfo, r.member, r.switchProgress.connectionStats) + chatModel.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.switchProgress.connectionStats) is CR.ContactRatchetSync -> - chatModel.updateContactConnectionStats(r.contact, r.ratchetSyncProgress.connectionStats) + chatModel.updateContactConnectionStats(rhId, r.contact, r.ratchetSyncProgress.connectionStats) is CR.GroupMemberRatchetSync -> - chatModel.updateGroupMemberConnectionStats(r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats) + chatModel.updateGroupMemberConnectionStats(rhId, r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats) is CR.RemoteHostSessionCode -> { chatModel.newRemoteHostPairing.value = r.remoteHost_ to RemoteHostSessionState.PendingConfirmation(r.sessionCode) } @@ -1850,9 +1838,11 @@ object ChatController { switchUIRemoteHost(r.remoteHost.remoteHostId) } is CR.RemoteHostStopped -> { - chatModel.currentRemoteHost.value = null chatModel.newRemoteHostPairing.value = null - switchUIRemoteHost(null) + if (chatModel.currentRemoteHost.value != null) { + chatModel.currentRemoteHost.value = null + switchUIRemoteHost(null) + } } is CR.RemoteCtrlFound -> { // TODO multicast @@ -1897,11 +1887,11 @@ object ChatController { val m = chatModel m.remoteCtrlSession.value = null withBGApi { - val users = listUsers() + val users = listUsers(null) m.users.clear() m.users.addAll(users) - getUserChatData() - val statuses = apiGetNetworkStatuses() + getUserChatData(null) + val statuses = apiGetNetworkStatuses(null) if (statuses != null) { chatModel.networkStatuses.clear() val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap() @@ -1911,7 +1901,7 @@ object ChatController { } private fun activeUser(rhId: Long?, user: UserLike): Boolean = - rhId == chatModel.currentRemoteHost.value?.remoteHostId && user.userId == chatModel.currentUser.value?.userId + rhId == chatModel.remoteHostId && user.userId == chatModel.currentUser.value?.userId private fun withCall(r: CR, contact: Contact, perform: (Call) -> Unit) { val call = chatModel.activeCall.value @@ -1929,20 +1919,20 @@ object ChatController { } } - suspend fun leaveGroup(groupId: Long) { - val groupInfo = apiLeaveGroup(groupId) + suspend fun leaveGroup(rh: Long?, groupId: Long) { + val groupInfo = apiLeaveGroup(rh, groupId) if (groupInfo != null) { - chatModel.updateGroup(groupInfo) + chatModel.updateGroup(rh, groupInfo) } } - private suspend fun chatItemSimpleUpdate(rhId: Long?, user: UserLike, aChatItem: AChatItem) { + private suspend fun chatItemSimpleUpdate(rh: Long?, user: UserLike, aChatItem: AChatItem) { val cInfo = aChatItem.chatInfo val cItem = aChatItem.chatItem val notify = { ntfManager.notifyMessageReceived(user, cInfo, cItem) } - if (!activeUser(rhId, user)) { + if (!activeUser(rh, user)) { notify() - } else if (chatModel.upsertChatItem(cInfo, cItem)) { + } else if (chatModel.upsertChatItem(rh, cInfo, cItem)) { notify() } } @@ -1969,22 +1959,23 @@ object ChatController { } suspend fun switchUIRemoteHost(rhId: Long?) { + // TODO lock the switch so that two switches can't run concurrently? chatModel.chatId.value = null chatModel.currentRemoteHost.value = switchRemoteHost(rhId) reloadRemoteHosts() - val user = apiGetActiveUser() - val users = listUsers() + val user = apiGetActiveUser(rhId) + val users = listUsers(rhId) chatModel.users.clear() chatModel.users.addAll(users) chatModel.currentUser.value = user chatModel.userCreated.value = true - val statuses = apiGetNetworkStatuses() + val statuses = apiGetNetworkStatuses(rhId) if (statuses != null) { chatModel.networkStatuses.clear() val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap() chatModel.networkStatuses.putAll(ss) } - getUserChatData() + getUserChatData(rhId) } fun getXFTPCfg(): XFTPFileConfig { @@ -2540,6 +2531,7 @@ data class UserProtocolServers( @Serializable data class ServerCfg( + val remoteHostId: Long? = null, val server: String, val preset: Boolean, val tested: Boolean? = null, @@ -3610,7 +3602,7 @@ private fun parseChatData(chat: JsonElement): Chat { val chatItems: List = chat.jsonObject["chatItems"]!!.jsonArray.map { decodeObject(ChatItem.serializer(), it) ?: parseChatItem(it) } - return Chat(chatInfo, chatItems, chatStats) + return Chat(remoteHostId = null, chatInfo, chatItems, chatStats) } private fun parseChatItem(j: JsonElement): ChatItem { @@ -3699,7 +3691,6 @@ sealed class CR { @Serializable @SerialName("chatItemNotChanged") class ChatItemNotChanged(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("chatItemReaction") class ChatItemReaction(val user: UserRef, val added: Boolean, val reaction: ACIReaction): CR() @Serializable @SerialName("chatItemDeleted") class ChatItemDeleted(val user: UserRef, val deletedChatItem: AChatItem, val toChatItem: AChatItem? = null, val byUser: Boolean): CR() - @Serializable @SerialName("contactsList") class ContactsList(val user: UserRef, val contacts: List): CR() // group events @Serializable @SerialName("groupCreated") class GroupCreated(val user: UserRef, val groupInfo: GroupInfo): CR() @Serializable @SerialName("sentGroupInvitation") class SentGroupInvitation(val user: UserRef, val groupInfo: GroupInfo, val contact: Contact, val member: GroupMember): CR() @@ -3853,7 +3844,6 @@ sealed class CR { is ChatItemNotChanged -> "chatItemNotChanged" is ChatItemReaction -> "chatItemReaction" is ChatItemDeleted -> "chatItemDeleted" - is ContactsList -> "contactsList" is GroupCreated -> "groupCreated" is SentGroupInvitation -> "sentGroupInvitation" is UserAcceptedGroupSent -> "userAcceptedGroupSent" @@ -4001,7 +3991,6 @@ sealed class CR { is ChatItemNotChanged -> withUser(user, json.encodeToString(chatItem)) is ChatItemReaction -> withUser(user, "added: $added\n${json.encodeToString(reaction)}") is ChatItemDeleted -> withUser(user, "deletedChatItem:\n${json.encodeToString(deletedChatItem)}\ntoChatItem:\n${json.encodeToString(toChatItem)}\nbyUser: $byUser") - is ContactsList -> withUser(user, json.encodeToString(contacts)) is GroupCreated -> withUser(user, json.encodeToString(groupInfo)) is SentGroupInvitation -> withUser(user, "groupInfo: $groupInfo\ncontact: $contact\nmember: $member") is UserAcceptedGroupSent -> json.encodeToString(groupInfo) @@ -4138,28 +4127,29 @@ sealed class GroupLinkPlan { abstract class TerminalItem { abstract val id: Long + abstract val remoteHostId: Long? val date: Instant = Clock.System.now() abstract val label: String abstract val details: String - class Cmd(override val id: Long, val cmd: CC): TerminalItem() { + class Cmd(override val id: Long, override val remoteHostId: Long?, val cmd: CC): TerminalItem() { override val label get() = "> ${cmd.cmdString}" override val details get() = cmd.cmdString } - class Resp(override val id: Long, val resp: CR): TerminalItem() { + class Resp(override val id: Long, override val remoteHostId: Long?, val resp: CR): TerminalItem() { override val label get() = "< ${resp.responseType}" override val details get() = resp.details } companion object { val sampleData = listOf( - Cmd(0, CC.ShowActiveUser()), - Resp(1, CR.ActiveUser(User.sampleData)) + Cmd(0, null, CC.ShowActiveUser()), + Resp(1, null, CR.ActiveUser(User.sampleData)) ) - fun cmd(c: CC) = Cmd(System.currentTimeMillis(), c) - fun resp(r: CR) = Resp(System.currentTimeMillis(), r) + fun cmd(rhId: Long?, c: CC) = Cmd(System.currentTimeMillis(), rhId, c) + fun resp(rhId: Long?, r: CR) = Resp(System.currentTimeMillis(), rhId, r) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index c32137ee61..3d3a91cb32 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -53,7 +53,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat } else if (startChat) { // If we migrated successfully means previous re-encryption process on database level finished successfully too if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null) - val user = chatController.apiGetActiveUser() + val user = chatController.apiGetActiveUser(null) if (user == null) { chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt index a03df5addb..06925e28a1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/NtfManager.kt @@ -49,7 +49,8 @@ abstract class NtfManager { null } val apiId = chatId.replace("<@", "").toLongOrNull() ?: return - acceptContactRequest(incognito, apiId, cInfo, isCurrentUser, ChatModel) + // TODO include remote host in notification + acceptContactRequest(null, incognito, apiId, cInfo, isCurrentUser, ChatModel) cancelNotificationsForChat(chatId) } @@ -57,11 +58,12 @@ abstract class NtfManager { withBGApi { awaitChatStartedIfNeeded(chatModel) if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) { - chatModel.controller.changeActiveUser(userId, null) + // TODO include remote host ID in desktop notifications? + chatModel.controller.changeActiveUser(null, userId, null) } val cInfo = chatModel.getChat(chatId)?.chatInfo chatModel.clearOverlays.value = true - if (cInfo != null && (cInfo is ChatInfo.Direct || cInfo is ChatInfo.Group)) openChat(cInfo, chatModel) + if (cInfo != null && (cInfo is ChatInfo.Direct || cInfo is ChatInfo.Group)) openChat(null, cInfo, chatModel) } } @@ -69,7 +71,8 @@ abstract class NtfManager { withBGApi { awaitChatStartedIfNeeded(chatModel) if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) { - chatModel.controller.changeActiveUser(userId, null) + // TODO include remote host ID in desktop notifications? + chatModel.controller.changeActiveUser(null, userId, null) } chatModel.chatId.value = null chatModel.clearOverlays.value = true diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index a471b5645e..ec2082557a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -47,13 +47,14 @@ private fun sendCommand(chatModel: ChatModel, composeState: MutableState) { val clipboard = LocalClipboardManager.current LazyColumn(state = listState, reverseLayout = true) { items(reversedTerminalItems) { item -> + val rhId = item.remoteHostId + val rhIdStr = if (rhId == null) "" else "$rhId " Text( - "${item.date.toString().subSequence(11, 19)} ${item.label}", + "$rhIdStr${item.date.toString().subSequence(11, 19)} ${item.label}", style = TextStyle(fontFamily = FontFamily.Monospace, fontSize = 18.sp, color = MaterialTheme.colors.primary), maxLines = 1, overflow = TextOverflow.Ellipsis, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 504ecac89d..fb15f0aba4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -170,18 +170,19 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () -> Unit) { withApi { + val rhId = chatModel.remoteHostId val user = chatModel.controller.apiCreateActiveUser( - Profile(displayName.trim(), "", null) + rhId, Profile(displayName.trim(), "", null) ) ?: return@withApi chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { chatModel.controller.startChat(user) chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress) } else { - val users = chatModel.controller.listUsers() + val users = chatModel.controller.listUsers(rhId) chatModel.users.clear() chatModel.users.addAll(users) - chatModel.controller.getUserChatData() + chatModel.controller.getUserChatData(rhId) close() } } @@ -190,7 +191,7 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () -> Unit) { withApi { chatModel.controller.apiCreateActiveUser( - Profile(displayName.trim(), "", null) + null, Profile(displayName.trim(), "", null) ) ?: return@withApi val onboardingStage = chatModel.controller.appPrefs.onboardingStage if (chatModel.users.isEmpty()) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index f601776f98..d0c9a6e4c6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -43,6 +43,7 @@ class CallManager(val chatModel: ChatModel) { private fun justAcceptIncomingCall(invitation: RcvCallInvitation) { with (chatModel) { activeCall.value = Call( + remoteHostId = invitation.remoteHostId, contact = invitation.contact, callState = CallState.InvitationAccepted, localMedia = invitation.callType.media, @@ -76,7 +77,7 @@ class CallManager(val chatModel: ChatModel) { Log.d(TAG, "CallManager.endCall: ending call...") callCommand.add(WCallCommand.End) showCallView.value = false - controller.apiEndCall(call.contact) + controller.apiEndCall(call.remoteHostId, call.contact) activeCall.value = null } } @@ -90,7 +91,7 @@ class CallManager(val chatModel: ChatModel) { ntfManager.cancelCallNotification() } withApi { - if (!controller.apiRejectCall(invitation.contact)) { + if (!controller.apiRejectCall(invitation.remoteHostId, invitation.contact)) { Log.e(TAG, "apiRejectCall error") } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt index 4be49d4c07..64904ba7a2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt @@ -11,6 +11,7 @@ import java.util.* import kotlin.collections.ArrayList data class Call( + val remoteHostId: Long? = null, val contact: Contact, val callState: CallState, val localMedia: CallMediaType, @@ -95,7 +96,14 @@ sealed class WCallResponse { @Serializable data class WebRTCSession(val rtcSession: String, val rtcIceCandidates: String) @Serializable data class WebRTCExtraInfo(val rtcIceCandidates: String) @Serializable data class CallType(val media: CallMediaType, val capabilities: CallCapabilities) -@Serializable data class RcvCallInvitation(val user: User, val contact: Contact, val callType: CallType, val sharedKey: String? = null, val callTs: Instant) { +@Serializable data class RcvCallInvitation( + val remoteHostId: Long? = null, + val user: User, + val contact: Contact, + val callType: CallType, + val sharedKey: String? = null, + val callTs: Instant +) { val callTypeText: String get() = generalGetString(when(callType.media) { CallMediaType.Video -> if (sharedKey == null) MR.strings.video_call_no_encryption else MR.strings.encrypted_video_call CallMediaType.Audio -> if (sharedKey == null) MR.strings.audio_call_no_encryption else MR.strings.encrypted_audio_call diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index b7c5e66a68..5816c89524 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -61,6 +61,7 @@ fun ChatInfoView( val contactNetworkStatus = remember(chatModel.networkStatuses.toMap(), contact) { mutableStateOf(chatModel.contactNetworkStatus(contact)) } + val chatRh = chat.remoteHostId val sendReceipts = remember(contact.id) { mutableStateOf(SendReceipts.fromBool(contact.chatSettings.sendRcpts, currentUser.sendRcptsContacts)) } ChatInfoLayout( chat, @@ -81,25 +82,25 @@ fun ChatInfoView( connectionCode, developerTools, onLocalAliasChanged = { - setContactAlias(chat.chatInfo.apiId, it, chatModel) + setContactAlias(chat, it, chatModel) }, openPreferences = { ModalManager.end.showCustomModal { close -> val user = chatModel.currentUser.value if (user != null) { - ContactPreferencesView(chatModel, user, contact.contactId, close) + ContactPreferencesView(chatModel, user, chatRh, contact.contactId, close) } } }, - deleteContact = { deleteContactDialog(chat.chatInfo, chatModel, close) }, - clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) }, + deleteContact = { deleteContactDialog(chat, chatModel, close) }, + clearChat = { clearChatDialog(chat, chatModel, close) }, switchContactAddress = { showSwitchAddressAlert(switchAddress = { withApi { - val cStats = chatModel.controller.apiSwitchContact(contact.contactId) + val cStats = chatModel.controller.apiSwitchContact(chatRh, contact.contactId) connStats.value = cStats if (cStats != null) { - chatModel.updateContactConnectionStats(contact, cStats) + chatModel.updateContactConnectionStats(chatRh, contact, cStats) } close.invoke() } @@ -108,20 +109,20 @@ fun ChatInfoView( abortSwitchContactAddress = { showAbortSwitchAddressAlert(abortSwitchAddress = { withApi { - val cStats = chatModel.controller.apiAbortSwitchContact(contact.contactId) + val cStats = chatModel.controller.apiAbortSwitchContact(chatRh, contact.contactId) connStats.value = cStats if (cStats != null) { - chatModel.updateContactConnectionStats(contact, cStats) + chatModel.updateContactConnectionStats(chatRh, contact, cStats) } } }) }, syncContactConnection = { withApi { - val cStats = chatModel.controller.apiSyncContactRatchet(contact.contactId, force = false) + val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) connStats.value = cStats if (cStats != null) { - chatModel.updateContactConnectionStats(contact, cStats) + chatModel.updateContactConnectionStats(chatRh, contact, cStats) } close.invoke() } @@ -129,10 +130,10 @@ fun ChatInfoView( syncContactConnectionForce = { showSyncConnectionForceAlert(syncConnectionForce = { withApi { - val cStats = chatModel.controller.apiSyncContactRatchet(contact.contactId, force = true) + val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = true) connStats.value = cStats if (cStats != null) { - chatModel.updateContactConnectionStats(contact, cStats) + chatModel.updateContactConnectionStats(chatRh, contact, cStats) } close.invoke() } @@ -146,9 +147,10 @@ fun ChatInfoView( connectionCode, ct.verified, verify = { code -> - chatModel.controller.apiVerifyContact(ct.contactId, code)?.let { r -> + chatModel.controller.apiVerifyContact(chatRh, ct.contactId, code)?.let { r -> val (verified, existingCode) = r chatModel.updateContact( + chatRh, ct.copy( activeConn = ct.activeConn?.copy( connectionCode = if (verified) SecurityCode(existingCode, Clock.System.now()) else null @@ -195,7 +197,8 @@ sealed class SendReceipts { } } -fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { +fun deleteContactDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? = null) { + val chatInfo = chat.chatInfo AlertManager.shared.showAlertDialogButtonsColumn( title = generalGetString(MR.strings.delete_contact_question), text = AnnotatedString(generalGetString(MR.strings.delete_contact_all_messages_deleted_cannot_undo_warning)), @@ -206,7 +209,7 @@ fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> SectionItemView({ AlertManager.shared.hideAlert() withApi { - deleteContact(chatInfo, chatModel, close, notify = true) + deleteContact(chat, chatModel, close, notify = true) } }) { Text(generalGetString(MR.strings.delete_and_notify_contact), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) @@ -215,7 +218,7 @@ fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> SectionItemView({ AlertManager.shared.hideAlert() withApi { - deleteContact(chatInfo, chatModel, close, notify = false) + deleteContact(chat, chatModel, close, notify = false) } }) { Text(generalGetString(MR.strings.delete_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) @@ -225,7 +228,7 @@ fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> SectionItemView({ AlertManager.shared.hideAlert() withApi { - deleteContact(chatInfo, chatModel, close) + deleteContact(chat, chatModel, close) } }) { Text(generalGetString(MR.strings.delete_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) @@ -242,11 +245,13 @@ fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> ) } -fun deleteContact(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)?, notify: Boolean? = null) { +fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, notify: Boolean? = null) { + val chatInfo = chat.chatInfo withApi { - val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId, notify) + val chatRh = chat.remoteHostId + val r = chatModel.controller.apiDeleteChat(chatRh, chatInfo.chatType, chatInfo.apiId, notify) if (r) { - chatModel.removeChat(chatInfo.id) + chatModel.removeChat(chatRh, chatInfo.id) if (chatModel.chatId.value == chatInfo.id) { chatModel.chatId.value = null ModalManager.end.closeModals() @@ -257,16 +262,18 @@ fun deleteContact(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? } } -fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { +fun clearChatDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? = null) { + val chatInfo = chat.chatInfo AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.clear_chat_question), text = generalGetString(MR.strings.clear_chat_warning), confirmText = generalGetString(MR.strings.clear_verb), onConfirm = { withApi { - val updatedChatInfo = chatModel.controller.apiClearChat(chatInfo.chatType, chatInfo.apiId) + val chatRh = chat.remoteHostId + val updatedChatInfo = chatModel.controller.apiClearChat(chatRh, chatInfo.chatType, chatInfo.apiId) if (updatedChatInfo != null) { - chatModel.clearChat(updatedChatInfo) + chatModel.clearChat(chatRh, updatedChatInfo) ntfManager.cancelNotificationsForChat(chatInfo.id) close?.invoke() } @@ -669,9 +676,10 @@ fun ShareAddressButton(onClick: () -> Unit) { ) } -private fun setContactAlias(contactApiId: Long, localAlias: String, chatModel: ChatModel) = withApi { - chatModel.controller.apiSetContactAlias(contactApiId, localAlias)?.let { - chatModel.updateContact(it) +private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withApi { + val chatRh = chat.remoteHostId + chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let { + chatModel.updateContact(chatRh, it) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 7097c77d1a..862212217f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -46,7 +46,6 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: val activeChat = remember { mutableStateOf(chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatId }) } val searchText = rememberSaveable { mutableStateOf("") } val user = chatModel.currentUser.value - val rhId = remember { chatModel.currentRemoteHost }.value?.remoteHostId val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() val composeState = rememberSaveable(saver = ComposeState.saver()) { mutableStateOf( @@ -101,11 +100,12 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } } val view = LocalMultiplatformView() - if (activeChat.value == null || user == null) { + val chat = activeChat.value + if (chat == null || user == null) { chatModel.chatId.value = null ModalManager.end.closeModals() } else { - val chat = activeChat.value!! + val chatRh = chat.remoteHostId // 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 unreadCount = remember { @@ -167,11 +167,11 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: var preloadedCode: String? = null var preloadedLink: Pair? = null if (chat.chatInfo is ChatInfo.Direct) { - preloadedContactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId) - preloadedCode = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId)?.second + preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) + preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second } else if (chat.chatInfo is ChatInfo.Group) { - setGroupMembers(chat.chatInfo.groupInfo, chatModel) - preloadedLink = chatModel.controller.apiGetGroupLink(chat.chatInfo.groupInfo.groupId) + setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) + preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) } ModalManager.end.showModalCloseable(true) { close -> val chat = remember { activeChat }.value @@ -179,20 +179,20 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: var contactInfo: Pair? by remember { mutableStateOf(preloadedContactInfo) } var code: String? by remember { mutableStateOf(preloadedCode) } KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) { - contactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId) + contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) preloadedContactInfo = contactInfo - code = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId)?.second + code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second preloadedCode = code } ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close) } else if (chat?.chatInfo is ChatInfo.Group) { var link: Pair? by remember(chat.id) { mutableStateOf(preloadedLink) } KeyChangeEffect(chat.id) { - setGroupMembers((chat.chatInfo as ChatInfo.Group).groupInfo, chatModel) - link = chatModel.controller.apiGetGroupLink(chat.chatInfo.groupInfo.groupId) + setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel) + link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) preloadedLink = link } - GroupChatInfoView(chatModel, link?.first, link?.second, { + GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, { link = it preloadedLink = it }, close) @@ -203,19 +203,19 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: showMemberInfo = { groupInfo: GroupInfo, member: GroupMember -> hideKeyboard(view) withApi { - val r = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId) + val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) val stats = r?.second val (_, code) = if (member.memberActive) { - val memCode = chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) + val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId) member to memCode?.second } else { member to null } - setGroupMembers(groupInfo, chatModel) + setGroupMembers(chatRh, groupInfo, chatModel) ModalManager.end.closeModals() ModalManager.end.showModalCloseable(true) { close -> remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem -> - GroupMemberInfoView(groupInfo, mem, stats, code, chatModel, close, close) + GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close) } } } @@ -226,7 +226,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: if (c != null && firstId != null) { withApi { Log.d(TAG, "TODOCHAT: loadPrevMessages: loading for ${c.id}, current chatId ${ChatModel.chatId.value}, size was ${ChatModel.chatItems.size}") - apiLoadPrevMessages(c.chatInfo, chatModel, firstId, searchText.value) + apiLoadPrevMessages(c, chatModel, firstId, searchText.value) Log.d(TAG, "TODOCHAT: loadPrevMessages: loaded for ${c.id}, current chatId ${ChatModel.chatId.value}, size now ${ChatModel.chatItems.size}") } } @@ -242,6 +242,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: val toChatItem: ChatItem? if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) { val r = chatModel.controller.apiDeleteMemberChatItem( + chatRh, groupId = groupInfo.groupId, groupMemberId = groupMember.groupMemberId, itemId = itemId @@ -250,6 +251,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: toChatItem = r?.second } else { val r = chatModel.controller.apiDeleteChatItem( + chatRh, type = cInfo.chatType, id = cInfo.apiId, itemId = itemId, @@ -259,9 +261,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: toChatItem = r?.toChatItem?.chatItem } if (toChatItem == null && deletedChatItem != null) { - chatModel.removeChatItem(cInfo, deletedChatItem) + chatModel.removeChatItem(chatRh, cInfo, deletedChatItem) } else if (toChatItem != null) { - chatModel.upsertChatItem(cInfo, toChatItem) + chatModel.upsertChatItem(chatRh, cInfo, toChatItem) } } }, @@ -272,27 +274,27 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: val deletedItems: ArrayList = arrayListOf() for (itemId in itemIds) { val di = chatModel.controller.apiDeleteChatItem( - chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal + chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal )?.deletedChatItem?.chatItem if (di != null) { deletedItems.add(di) } } for (di in deletedItems) { - chatModel.removeChatItem(chatInfo, di) + chatModel.removeChatItem(chatRh, chatInfo, di) } } } }, receiveFile = { fileId, encrypted -> - withApi { chatModel.controller.receiveFile(rhId, user, fileId, encrypted) } + withApi { chatModel.controller.receiveFile(chatRh, user, fileId, encrypted) } }, cancelFile = { fileId -> - withApi { chatModel.controller.cancelFile(rhId, user, fileId) } + withApi { chatModel.controller.cancelFile(chatRh, user, fileId) } }, joinGroup = { groupId, onComplete -> withApi { - chatModel.controller.apiJoinGroup(groupId) + chatModel.controller.apiJoinGroup(chatRh, groupId) onComplete.invoke() } }, @@ -300,7 +302,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: withBGApi { val cInfo = chat.chatInfo if (cInfo is ChatInfo.Direct) { - chatModel.activeCall.value = Call(contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media) + chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media) chatModel.showCallView.value = true chatModel.callCommand.add(WCallCommand.Capabilities(media)) } @@ -321,48 +323,48 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: }, acceptFeature = { contact, feature, param -> withApi { - chatModel.controller.allowFeatureToContact(contact, feature, param) + chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param) } }, openDirectChat = { contactId -> withApi { - openDirectChat(contactId, chatModel) + openDirectChat(chatRh, contactId, chatModel) } }, updateContactStats = { contact -> withApi { - val r = chatModel.controller.apiContactInfo(chat.chatInfo.apiId) + val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) if (r != null) { val contactStats = r.first if (contactStats != null) - chatModel.updateContactConnectionStats(contact, contactStats) + chatModel.updateContactConnectionStats(chatRh, contact, contactStats) } } }, updateMemberStats = { groupInfo, member -> withApi { - val r = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId) + val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) if (r != null) { val memStats = r.second if (memStats != null) { - chatModel.updateGroupMemberConnectionStats(groupInfo, r.first, memStats) + chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats) } } } }, syncContactConnection = { contact -> withApi { - val cStats = chatModel.controller.apiSyncContactRatchet(contact.contactId, force = false) + val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) if (cStats != null) { - chatModel.updateContactConnectionStats(contact, cStats) + chatModel.updateContactConnectionStats(chatRh, contact, cStats) } } }, syncMemberConnection = { groupInfo, member -> withApi { - val r = chatModel.controller.apiSyncGroupMemberRatchet(groupInfo.apiId, member.groupMemberId, force = false) + val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false) if (r != null) { - chatModel.updateGroupMemberConnectionStats(groupInfo, r.first, r.second) + chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second) } } }, @@ -375,6 +377,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: setReaction = { cInfo, cItem, add, reaction -> withApi { val updatedCI = chatModel.controller.apiChatItemReaction( + rh = chatRh, type = cInfo.chatType, id = cInfo.apiId, itemId = cItem.id, @@ -388,10 +391,10 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: }, showItemDetails = { cInfo, cItem -> withApi { - val ciInfo = chatModel.controller.apiGetChatItemInfo(cInfo.chatType, cInfo.apiId, cItem.id) + val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id) if (ciInfo != null) { if (chat.chatInfo is ChatInfo.Group) { - setGroupMembers(chat.chatInfo.groupInfo, chatModel) + setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) } ModalManager.end.closeModals() ModalManager.end.showModal(endButtons = { ShareButton { @@ -405,28 +408,29 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: addMembers = { groupInfo -> hideKeyboard(view) withApi { - setGroupMembers(groupInfo, chatModel) + setGroupMembers(chatRh, groupInfo, chatModel) ModalManager.end.closeModals() ModalManager.end.showModalCloseable(true) { close -> - AddGroupMembersView(groupInfo, false, chatModel, close) + AddGroupMembersView(chatRh, groupInfo, false, chatModel, close) } } }, openGroupLink = { groupInfo -> hideKeyboard(view) withApi { - val link = chatModel.controller.apiGetGroupLink(groupInfo.groupId) + val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId) ModalManager.end.closeModals() ModalManager.end.showModalCloseable(true) { - GroupLinkView(chatModel, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null) + GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null) } } }, markRead = { range, unreadCountAfter -> - chatModel.markChatItemsRead(chat.chatInfo, range, unreadCountAfter) + chatModel.markChatItemsRead(chat, range, unreadCountAfter) ntfManager.cancelNotificationsForChat(chat.id) withBGApi { chatModel.controller.apiChatRead( + chatRh, chat.chatInfo.chatType, chat.chatInfo.apiId, range @@ -438,7 +442,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: if (searchText.value == value) return@ChatLayout val c = chatModel.getChat(chat.chatInfo.id) ?: return@ChatLayout withApi { - apiFindMessages(c.chatInfo, chatModel, value) + apiFindMessages(c, chatModel, value) searchText.value = value } }, @@ -1254,14 +1258,16 @@ private fun markUnreadChatAsRead(activeChat: MutableState, chatModel: Cha val chat = activeChat.value if (chat?.chatStats?.unreadChat != true) return withApi { + val chatRh = chat.remoteHostId val success = chatModel.controller.apiChatUnread( + chatRh, chat.chatInfo.chatType, chat.chatInfo.apiId, false ) if (success && chat.id == activeChat.value?.id) { activeChat.value = chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)) - chatModel.replaceChat(chat.id, activeChat.value!!) + chatModel.replaceChat(chatRh, chat.id, activeChat.value!!) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index a9b7014d51..b8076b1477 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -351,9 +351,10 @@ fun ComposeView( } } - suspend fun send(rhId: Long?, cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? { + suspend fun send(chat: Chat, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? { + val cInfo = chat.chatInfo val aChatItem = chatModel.controller.apiSendMessage( - rhId = rhId, + rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, file = file, @@ -363,7 +364,7 @@ fun ComposeView( ttl = ttl ) if (aChatItem != null) { - chatModel.addChatItem(cInfo, aChatItem.chatItem) + chatModel.addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem) return aChatItem.chatItem } if (file != null) removeFile(file.filePath) @@ -410,23 +411,25 @@ fun ComposeView( suspend fun sendMemberContactInvitation() { val mc = checkLinkPreview() - val contact = chatModel.controller.apiSendMemberContactInvitation(chat.chatInfo.apiId, mc) + val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc) if (contact != null) { - chatModel.updateContact(contact) + chatModel.updateContact(chat.remoteHostId, contact) } } - suspend fun updateMessage(ei: ChatItem, cInfo: ChatInfo, live: Boolean): ChatItem? { + suspend fun updateMessage(ei: ChatItem, chat: Chat, live: Boolean): ChatItem? { + val cInfo = chat.chatInfo val oldMsgContent = ei.content.msgContent if (oldMsgContent != null) { val updatedItem = chatModel.controller.apiUpdateChatItem( + rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, itemId = ei.meta.itemId, mc = updateMsgContent(oldMsgContent), live = live ) - if (updatedItem != null) chatModel.upsertChatItem(cInfo, updatedItem.chatItem) + if (updatedItem != null) chatModel.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem) return updatedItem?.chatItem } return null @@ -444,9 +447,9 @@ fun ComposeView( sent = null } else if (cs.contextItem is ComposeContextItem.EditingItem) { val ei = cs.contextItem.chatItem - sent = updateMessage(ei, cInfo, live) + sent = updateMessage(ei, chat, live) } else if (liveMessage != null && liveMessage.sent) { - sent = updateMessage(liveMessage.chatItem, cInfo, live) + sent = updateMessage(liveMessage.chatItem, chat, live) } else { val msgs: ArrayList = ArrayList() val files: ArrayList = ArrayList() @@ -528,7 +531,7 @@ fun ComposeView( localPath = file.filePath ) } - sent = send(remoteHost?.remoteHostId, cInfo, content, if (index == 0) quotedItemId else null, file, + sent = send(chat, content, if (index == 0) quotedItemId else null, file, live = if (content !is MsgContent.MCVoice && index == msgs.lastIndex) live else false, ttl = ttl ) @@ -538,7 +541,7 @@ fun ComposeView( cs.preview is ComposePreview.FilePreview || cs.preview is ComposePreview.VoicePreview) ) { - sent = send(remoteHost?.remoteHostId, cInfo, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) + sent = send(chat, MsgContent.MCText(msgText), quotedItemId, null, live, ttl) } } clearState(live) @@ -573,7 +576,7 @@ fun ComposeView( fun allowVoiceToContact() { val contact = (chat.chatInfo as ChatInfo.Direct?)?.contact ?: return withApi { - chatModel.controller.allowFeatureToContact(contact, ChatFeature.Voice) + chatModel.controller.allowFeatureToContact(chat.remoteHostId, contact, ChatFeature.Voice) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt index 465603d409..c12982ada5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt @@ -25,6 +25,7 @@ import chat.simplex.res.MR fun ContactPreferencesView( m: ChatModel, user: User, + rhId: Long?, contactId: Long, close: () -> Unit, ) { @@ -36,9 +37,9 @@ fun ContactPreferencesView( fun savePrefs(afterSave: () -> Unit = {}) { withApi { val prefs = contactFeaturesAllowedToPrefs(featuresAllowed) - val toContact = m.controller.apiSetContactPrefs(ct.contactId, prefs) + val toContact = m.controller.apiSetContactPrefs(rhId, ct.contactId, prefs) if (toContact != null) { - m.updateContact(toContact) + m.updateContact(rhId, toContact) currentFeaturesAllowed = featuresAllowed } afterSave() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index 90ab1b45ff..ff23d40b82 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -33,7 +33,7 @@ import chat.simplex.common.platform.* import chat.simplex.res.MR @Composable -fun AddGroupMembersView(groupInfo: GroupInfo, creatingGroup: Boolean = false, chatModel: ChatModel, close: () -> Unit) { +fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolean = false, chatModel: ChatModel, close: () -> Unit) { val selectedContacts = remember { mutableStateListOf() } val selectedRole = remember { mutableStateOf(GroupMemberRole.Member) } var allowModifyMembers by remember { mutableStateOf(true) } @@ -49,16 +49,16 @@ fun AddGroupMembersView(groupInfo: GroupInfo, creatingGroup: Boolean = false, ch searchText, openPreferences = { ModalManager.end.showCustomModal { close -> - GroupPreferencesView(chatModel, groupInfo.id, close) + GroupPreferencesView(chatModel, rhId, groupInfo.id, close) } }, inviteMembers = { allowModifyMembers = false withApi { for (contactId in selectedContacts) { - val member = chatModel.controller.apiAddMember(groupInfo.groupId, contactId, selectedRole.value) + val member = chatModel.controller.apiAddMember(rhId, groupInfo.groupId, contactId, selectedRole.value) if (member != null) { - chatModel.upsertGroupMember(groupInfo, member) + chatModel.upsertGroupMember(rhId, groupInfo, member) } else { break } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 8c9619703b..49d76d8ecd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -41,9 +41,10 @@ import kotlinx.coroutines.launch const val SMALL_GROUPS_RCPS_MEM_LIMIT: Int = 20 @Composable -fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberRole: GroupMemberRole?, onGroupLinkUpdated: (Pair?) -> Unit, close: () -> Unit) { +fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLink: String?, groupLinkMemberRole: GroupMemberRole?, onGroupLinkUpdated: (Pair?) -> Unit, close: () -> Unit) { BackHandler(onBack = close) - val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value } + // TODO derivedStateOf? + val chat = chatModel.chats.firstOrNull { ch -> ch.id == chatId && ch.remoteHostId == rhId } val currentUser = chatModel.currentUser.value val developerTools = chatModel.controller.appPrefs.developerTools.get() if (chat != null && chat.chatInfo is ChatInfo.Group && currentUser != null) { @@ -68,25 +69,25 @@ fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberR groupLink, addMembers = { withApi { - setGroupMembers(groupInfo, chatModel) + setGroupMembers(rhId, groupInfo, chatModel) ModalManager.end.showModalCloseable(true) { close -> - AddGroupMembersView(groupInfo, false, chatModel, close) + AddGroupMembersView(rhId, groupInfo, false, chatModel, close) } } }, showMemberInfo = { member -> withApi { - val r = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId) + val r = chatModel.controller.apiGroupMemberInfo(rhId, groupInfo.groupId, member.groupMemberId) val stats = r?.second val (_, code) = if (member.memberActive) { - val memCode = chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) + val memCode = chatModel.controller.apiGetGroupMemberCode(rhId, groupInfo.apiId, member.groupMemberId) member to memCode?.second } else { member to null } ModalManager.end.showModalCloseable(true) { closeCurrent -> remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem -> - GroupMemberInfoView(groupInfo, mem, stats, code, chatModel, closeCurrent) { + GroupMemberInfoView(rhId, groupInfo, mem, stats, code, chatModel, closeCurrent) { closeCurrent() close() } @@ -95,31 +96,33 @@ fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberR } }, editGroupProfile = { - ModalManager.end.showCustomModal { close -> GroupProfileView(groupInfo, chatModel, close) } + ModalManager.end.showCustomModal { close -> GroupProfileView(rhId, groupInfo, chatModel, close) } }, addOrEditWelcomeMessage = { - ModalManager.end.showCustomModal { close -> GroupWelcomeView(chatModel, groupInfo, close) } + ModalManager.end.showCustomModal { close -> GroupWelcomeView(chatModel, rhId, groupInfo, close) } }, openPreferences = { ModalManager.end.showCustomModal { close -> GroupPreferencesView( chatModel, + rhId, chat.id, close ) } }, - deleteGroup = { deleteGroupDialog(chat.chatInfo, groupInfo, chatModel, close) }, - clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) }, - leaveGroup = { leaveGroupDialog(groupInfo, chatModel, close) }, + deleteGroup = { deleteGroupDialog(chat, groupInfo, chatModel, close) }, + clearChat = { clearChatDialog(chat, chatModel, close) }, + leaveGroup = { leaveGroupDialog(rhId, groupInfo, chatModel, close) }, manageGroupLink = { - ModalManager.end.showModal { GroupLinkView(chatModel, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) } + ModalManager.end.showModal { GroupLinkView(chatModel, rhId, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) } } ) } } -fun deleteGroupDialog(chatInfo: ChatInfo, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { +fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { + val chatInfo = chat.chatInfo val alertTextKey = if (groupInfo.membership.memberCurrent) MR.strings.delete_group_for_all_members_cannot_undo_warning else MR.strings.delete_group_for_self_cannot_undo_warning @@ -129,9 +132,9 @@ fun deleteGroupDialog(chatInfo: ChatInfo, groupInfo: GroupInfo, chatModel: ChatM confirmText = generalGetString(MR.strings.delete_verb), onConfirm = { withApi { - val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId) + val r = chatModel.controller.apiDeleteChat(chat.remoteHostId, chatInfo.chatType, chatInfo.apiId) if (r) { - chatModel.removeChat(chatInfo.id) + chatModel.removeChat(chat.remoteHostId, chatInfo.id) if (chatModel.chatId.value == chatInfo.id) { chatModel.chatId.value = null ModalManager.end.closeModals() @@ -145,14 +148,14 @@ fun deleteGroupDialog(chatInfo: ChatInfo, groupInfo: GroupInfo, chatModel: ChatM ) } -fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { +fun leaveGroupDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.leave_group_question), text = generalGetString(MR.strings.you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved), confirmText = generalGetString(MR.strings.leave_group_button), onConfirm = { withApi { - chatModel.controller.leaveGroup(groupInfo.groupId) + chatModel.controller.leaveGroup(rhId, groupInfo.groupId) close?.invoke() } }, @@ -160,16 +163,16 @@ fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> U ) } -private fun removeMemberAlert(groupInfo: GroupInfo, mem: GroupMember) { +private fun removeMemberAlert(rhId: Long?, groupInfo: GroupInfo, mem: GroupMember) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.button_remove_member_question), text = generalGetString(MR.strings.member_will_be_removed_from_group_cannot_be_undone), confirmText = generalGetString(MR.strings.remove_member_confirmation), onConfirm = { withApi { - val updatedMember = chatModel.controller.apiRemoveMember(groupInfo.groupId, mem.groupMemberId) + val updatedMember = chatModel.controller.apiRemoveMember(rhId, groupInfo.groupId, mem.groupMemberId) if (updatedMember != null) { - chatModel.upsertGroupMember(groupInfo, updatedMember) + chatModel.upsertGroupMember(rhId, groupInfo, updatedMember) } } }, @@ -260,7 +263,7 @@ fun GroupChatInfoLayout( Divider() val showMenu = remember { mutableStateOf(false) } SectionItemViewLongClickable({ showMemberInfo(member) }, { showMenu.value = true }, minHeight = 54.dp) { - DropDownMenuForMember(member, groupInfo, showMenu) + DropDownMenuForMember(chat.remoteHostId, member, groupInfo, showMenu) MemberRow(member, onClick = { showMemberInfo(member) }) } } @@ -413,22 +416,22 @@ private fun MemberVerifiedShield() { } @Composable -private fun DropDownMenuForMember(member: GroupMember, groupInfo: GroupInfo, showMenu: MutableState) { +private fun DropDownMenuForMember(rhId: Long?, member: GroupMember, groupInfo: GroupInfo, showMenu: MutableState) { DefaultDropdownMenu(showMenu) { if (member.canBeRemoved(groupInfo)) { ItemAction(stringResource(MR.strings.remove_member_button), painterResource(MR.images.ic_delete), color = MaterialTheme.colors.error, onClick = { - removeMemberAlert(groupInfo, member) + removeMemberAlert(rhId, groupInfo, member) showMenu.value = false }) } if (member.memberSettings.showMessages) { ItemAction(stringResource(MR.strings.block_member_button), painterResource(MR.images.ic_back_hand), color = MaterialTheme.colors.error, onClick = { - blockMemberAlert(groupInfo, member) + blockMemberAlert(rhId, groupInfo, member) showMenu.value = false }) } else { ItemAction(stringResource(MR.strings.unblock_member_button), painterResource(MR.images.ic_do_not_touch), onClick = { - unblockMemberAlert(groupInfo, member) + unblockMemberAlert(rhId, groupInfo, member) showMenu.value = false }) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index 809c7c2fd6..02ce90243c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt @@ -25,6 +25,7 @@ import chat.simplex.res.MR @Composable fun GroupLinkView( chatModel: ChatModel, + rhId: Long?, groupInfo: GroupInfo, connReqContact: String?, memberRole: GroupMemberRole?, @@ -38,7 +39,7 @@ fun GroupLinkView( fun createLink() { creatingLink = true withApi { - val link = chatModel.controller.apiCreateGroupLink(groupInfo.groupId) + val link = chatModel.controller.apiCreateGroupLink(rhId, groupInfo.groupId) if (link != null) { groupLink = link.first groupLinkMemberRole.value = link.second @@ -62,7 +63,7 @@ fun GroupLinkView( val role = groupLinkMemberRole.value if (role != null) { withBGApi { - val link = chatModel.controller.apiGroupLinkMemberRole(groupInfo.groupId, role) + val link = chatModel.controller.apiGroupLinkMemberRole(rhId, groupInfo.groupId, role) if (link != null) { groupLink = link.first groupLinkMemberRole.value = link.second @@ -78,7 +79,7 @@ fun GroupLinkView( confirmText = generalGetString(MR.strings.delete_verb), onConfirm = { withApi { - val r = chatModel.controller.apiDeleteGroupLink(groupInfo.groupId) + val r = chatModel.controller.apiDeleteGroupLink(rhId, groupInfo.groupId) if (r) { groupLink = null onGroupLinkUpdated?.invoke(null) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 9f52f61deb..00b236c7dd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -40,6 +40,7 @@ import kotlinx.datetime.Clock @Composable fun GroupMemberInfoView( + rhId: Long?, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats?, @@ -49,7 +50,7 @@ fun GroupMemberInfoView( closeAll: () -> Unit, // Close all open windows up to ChatView ) { BackHandler(onBack = close) - val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value } + val chat = chatModel.chats.firstOrNull { ch -> ch.id == chatModel.chatId.value && ch.remoteHostId == rhId } val connStats = remember { mutableStateOf(connectionStats) } val developerTools = chatModel.controller.appPrefs.developerTools.get() var progressIndicator by remember { mutableStateOf(false) } @@ -66,7 +67,7 @@ fun GroupMemberInfoView( getContactChat = { chatModel.getContactChat(it) }, openDirectChat = { withApi { - val c = chatModel.controller.apiGetChat(ChatType.Direct, it) + val c = chatModel.controller.apiGetChat(rhId, ChatType.Direct, it) if (c != null) { if (chatModel.getContactChat(it) == null) { chatModel.addChat(c) @@ -82,9 +83,9 @@ fun GroupMemberInfoView( createMemberContact = { withApi { progressIndicator = true - val memberContact = chatModel.controller.apiCreateMemberContact(groupInfo.apiId, member.groupMemberId) + val memberContact = chatModel.controller.apiCreateMemberContact(rhId, groupInfo.apiId, member.groupMemberId) if (memberContact != null) { - val memberChat = Chat(ChatInfo.Direct(memberContact), chatItems = arrayListOf()) + val memberChat = Chat(remoteHostId = rhId, ChatInfo.Direct(memberContact), chatItems = arrayListOf()) chatModel.addChat(memberChat) openLoadedChat(memberChat, chatModel) closeAll() @@ -94,11 +95,11 @@ fun GroupMemberInfoView( } }, connectViaAddress = { connReqUri -> - connectViaMemberAddressAlert(connReqUri) + connectViaMemberAddressAlert(rhId, connReqUri) }, - blockMember = { blockMemberAlert(groupInfo, member) }, - unblockMember = { unblockMemberAlert(groupInfo, member) }, - removeMember = { removeMemberDialog(groupInfo, member, chatModel, close) }, + blockMember = { blockMemberAlert(rhId, groupInfo, member) }, + unblockMember = { unblockMemberAlert(rhId, groupInfo, member) }, + removeMember = { removeMemberDialog(rhId, groupInfo, member, chatModel, close) }, onRoleSelected = { if (it == newRole.value) return@GroupMemberInfoLayout val prevValue = newRole.value @@ -108,8 +109,8 @@ fun GroupMemberInfoView( }) { withApi { kotlin.runCatching { - val mem = chatModel.controller.apiMemberRole(groupInfo.groupId, member.groupMemberId, it) - chatModel.upsertGroupMember(groupInfo, mem) + val mem = chatModel.controller.apiMemberRole(rhId, groupInfo.groupId, member.groupMemberId, it) + chatModel.upsertGroupMember(rhId, groupInfo, mem) }.onFailure { newRole.value = prevValue } @@ -119,10 +120,10 @@ fun GroupMemberInfoView( switchMemberAddress = { showSwitchAddressAlert(switchAddress = { withApi { - val r = chatModel.controller.apiSwitchGroupMember(groupInfo.apiId, member.groupMemberId) + val r = chatModel.controller.apiSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId) if (r != null) { connStats.value = r.second - chatModel.updateGroupMemberConnectionStats(groupInfo, r.first, r.second) + chatModel.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) close.invoke() } } @@ -131,10 +132,10 @@ fun GroupMemberInfoView( abortSwitchMemberAddress = { showAbortSwitchAddressAlert(abortSwitchAddress = { withApi { - val r = chatModel.controller.apiAbortSwitchGroupMember(groupInfo.apiId, member.groupMemberId) + val r = chatModel.controller.apiAbortSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId) if (r != null) { connStats.value = r.second - chatModel.updateGroupMemberConnectionStats(groupInfo, r.first, r.second) + chatModel.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) close.invoke() } } @@ -142,10 +143,10 @@ fun GroupMemberInfoView( }, syncMemberConnection = { withApi { - val r = chatModel.controller.apiSyncGroupMemberRatchet(groupInfo.apiId, member.groupMemberId, force = false) + val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = false) if (r != null) { connStats.value = r.second - chatModel.updateGroupMemberConnectionStats(groupInfo, r.first, r.second) + chatModel.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) close.invoke() } } @@ -153,10 +154,10 @@ fun GroupMemberInfoView( syncMemberConnectionForce = { showSyncConnectionForceAlert(syncConnectionForce = { withApi { - val r = chatModel.controller.apiSyncGroupMemberRatchet(groupInfo.apiId, member.groupMemberId, force = true) + val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = true) if (r != null) { connStats.value = r.second - chatModel.updateGroupMemberConnectionStats(groupInfo, r.first, r.second) + chatModel.updateGroupMemberConnectionStats(rhId, groupInfo, r.first, r.second) close.invoke() } } @@ -170,9 +171,10 @@ fun GroupMemberInfoView( connectionCode, mem.verified, verify = { code -> - chatModel.controller.apiVerifyGroupMember(mem.groupId, mem.groupMemberId, code)?.let { r -> + chatModel.controller.apiVerifyGroupMember(rhId, mem.groupId, mem.groupMemberId, code)?.let { r -> val (verified, existingCode) = r chatModel.upsertGroupMember( + rhId, groupInfo, mem.copy( activeConn = mem.activeConn?.copy( @@ -196,16 +198,16 @@ fun GroupMemberInfoView( } } -fun removeMemberDialog(groupInfo: GroupInfo, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) { +fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.button_remove_member), text = generalGetString(MR.strings.member_will_be_removed_from_group_cannot_be_undone), confirmText = generalGetString(MR.strings.remove_member_confirmation), onConfirm = { withApi { - val removedMember = chatModel.controller.apiRemoveMember(member.groupId, member.groupMemberId) + val removedMember = chatModel.controller.apiRemoveMember(rhId, member.groupId, member.groupMemberId) if (removedMember != null) { - chatModel.upsertGroupMember(groupInfo, removedMember) + chatModel.upsertGroupMember(rhId, groupInfo, removedMember) } close?.invoke() } @@ -500,11 +502,11 @@ private fun updateMemberRoleDialog( ) } -fun connectViaMemberAddressAlert(connReqUri: String) { +fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) { try { val uri = URI(connReqUri) withApi { - planAndConnect(chatModel, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) + planAndConnect(chatModel, rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) } } catch (e: RuntimeException) { AlertManager.shared.showAlertMsg( @@ -514,39 +516,39 @@ fun connectViaMemberAddressAlert(connReqUri: String) { } } -fun blockMemberAlert(gInfo: GroupInfo, mem: GroupMember) { +fun blockMemberAlert(rhId: Long?, gInfo: GroupInfo, mem: GroupMember) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.block_member_question), text = generalGetString(MR.strings.block_member_desc).format(mem.chatViewName), confirmText = generalGetString(MR.strings.block_member_confirmation), onConfirm = { - toggleShowMemberMessages(gInfo, mem, false) + toggleShowMemberMessages(rhId, gInfo, mem, false) }, destructive = true, ) } -fun unblockMemberAlert(gInfo: GroupInfo, mem: GroupMember) { +fun unblockMemberAlert(rhId: Long?, gInfo: GroupInfo, mem: GroupMember) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.unblock_member_question), text = generalGetString(MR.strings.unblock_member_desc).format(mem.chatViewName), confirmText = generalGetString(MR.strings.unblock_member_confirmation), onConfirm = { - toggleShowMemberMessages(gInfo, mem, true) + toggleShowMemberMessages(rhId, gInfo, mem, true) }, ) } -fun toggleShowMemberMessages(gInfo: GroupInfo, member: GroupMember, showMessages: Boolean) { +fun toggleShowMemberMessages(rhId: Long?, gInfo: GroupInfo, member: GroupMember, showMessages: Boolean) { val updatedMemberSettings = member.memberSettings.copy(showMessages = showMessages) - updateMemberSettings(gInfo, member, updatedMemberSettings) + updateMemberSettings(rhId, gInfo, member, updatedMemberSettings) } -fun updateMemberSettings(gInfo: GroupInfo, member: GroupMember, memberSettings: GroupMemberSettings) { +fun updateMemberSettings(rhId: Long?, gInfo: GroupInfo, member: GroupMember, memberSettings: GroupMemberSettings) { withBGApi { - val success = ChatController.apiSetMemberSettings(gInfo.groupId, member.groupMemberId, memberSettings) + val success = ChatController.apiSetMemberSettings(rhId, gInfo.groupId, member.groupMemberId, memberSettings) if (success) { - ChatModel.upsertGroupMember(gInfo, member.copy(memberSettings = memberSettings)) + ChatModel.upsertGroupMember(rhId, gInfo, member.copy(memberSettings = memberSettings)) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index 4571a38c13..3cdfaad2d9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -21,8 +21,12 @@ import chat.simplex.common.model.* import chat.simplex.res.MR @Composable -fun GroupPreferencesView(m: ChatModel, chatId: String, close: () -> Unit,) { - val groupInfo = remember { derivedStateOf { (m.getChat(chatId)?.chatInfo as? ChatInfo.Group)?.groupInfo } } +fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () -> Unit,) { + val groupInfo = remember { derivedStateOf { + val ch = m.getChat(chatId) + val g = (ch?.chatInfo as? ChatInfo.Group)?.groupInfo + if (g == null || ch?.remoteHostId != rhId) null else g + }} val gInfo = groupInfo.value ?: return var preferences by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(gInfo.fullGroupPreferences) } var currentPreferences by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(preferences) } @@ -30,9 +34,9 @@ fun GroupPreferencesView(m: ChatModel, chatId: String, close: () -> Unit,) { fun savePrefs(afterSave: () -> Unit = {}) { withApi { val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences()) - val g = m.controller.apiUpdateGroup(gInfo.groupId, gp) + val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp) if (g != null) { - m.updateGroup(g) + m.updateGroup(rhId, g) currentPreferences = preferences } afterSave() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt index 5376cb092b..f92fd88dc0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt @@ -30,15 +30,15 @@ import kotlinx.coroutines.launch import java.net.URI @Composable -fun GroupProfileView(groupInfo: GroupInfo, chatModel: ChatModel, close: () -> Unit) { +fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, close: () -> Unit) { GroupProfileLayout( close = close, groupProfile = groupInfo.groupProfile, saveProfile = { p -> withApi { - val gInfo = chatModel.controller.apiUpdateGroup(groupInfo.groupId, p) + val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p) if (gInfo != null) { - chatModel.updateGroup(gInfo) + chatModel.updateGroup(rhId, gInfo) close.invoke() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt index 3be54376d5..577c19648d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt @@ -30,7 +30,7 @@ import chat.simplex.res.MR import kotlinx.coroutines.delay @Composable -fun GroupWelcomeView(m: ChatModel, groupInfo: GroupInfo, close: () -> Unit) { +fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: () -> Unit) { var gInfo by remember { mutableStateOf(groupInfo) } val welcomeText = remember { mutableStateOf(gInfo.groupProfile.description ?: "") } @@ -41,10 +41,10 @@ fun GroupWelcomeView(m: ChatModel, groupInfo: GroupInfo, close: () -> Unit) { welcome = null } val groupProfileUpdated = gInfo.groupProfile.copy(description = welcome) - val res = m.controller.apiUpdateGroup(gInfo.groupId, groupProfileUpdated) + val res = m.controller.apiUpdateGroup(rhId, gInfo.groupId, groupProfileUpdated) if (res != null) { gInfo = res - m.updateGroup(res) + m.updateGroup(rhId, res) welcomeText.value = welcome ?: "" } afterSave() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 13380a664b..7e81faf3d5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -62,7 +62,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { val contactNetworkStatus = chatModel.contactNetworkStatus(chat.chatInfo.contact) ChatListNavLinkLayout( chatLinkPreview = { ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, contactNetworkStatus, stopped, linkMode, inProgress = false, progressByTimeout = false) }, - click = { directChatAction(chat.chatInfo.contact, chatModel) }, + click = { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) }, dropdownMenuItems = { ContactMenuItems(chat, chat.chatInfo.contact, chatModel, showMenu, showMarkRead) }, showMenu, stopped, @@ -72,7 +72,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { is ChatInfo.Group -> ChatListNavLinkLayout( chatLinkPreview = { ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, stopped, linkMode, inProgress.value, progressByTimeout) }, - click = { if (!inProgress.value) groupChatAction(chat.chatInfo.groupInfo, chatModel, inProgress) }, + click = { if (!inProgress.value) groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel, inProgress) }, dropdownMenuItems = { GroupMenuItems(chat, chat.chatInfo.groupInfo, chatModel, showMenu, inProgress, showMarkRead) }, showMenu, stopped, @@ -81,8 +81,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { is ChatInfo.ContactRequest -> ChatListNavLinkLayout( chatLinkPreview = { ContactRequestView(chat.chatInfo) }, - click = { contactRequestAlertDialog(chat.chatInfo, chatModel) }, - dropdownMenuItems = { ContactRequestMenuItems(chat.chatInfo, chatModel, showMenu) }, + click = { contactRequestAlertDialog(chat.remoteHostId, chat.chatInfo, chatModel) }, + dropdownMenuItems = { ContactRequestMenuItems(chat.remoteHostId, chat.chatInfo, chatModel, showMenu) }, showMenu, stopped, selectedChat @@ -94,10 +94,10 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { ModalManager.center.closeModals() ModalManager.end.closeModals() ModalManager.center.showModalCloseable(true, showClose = appPlatform.isAndroid) { close -> - ContactConnectionInfoView(chatModel, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close) + ContactConnectionInfoView(chatModel, chat.remoteHostId, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close) } }, - dropdownMenuItems = { ContactConnectionMenuItems(chat.chatInfo, chatModel, showMenu) }, + dropdownMenuItems = { ContactConnectionMenuItems(chat.remoteHostId, chat.chatInfo, chatModel, showMenu) }, showMenu, stopped, selectedChat @@ -119,38 +119,38 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { } } -fun directChatAction(contact: Contact, chatModel: ChatModel) { +fun directChatAction(rhId: Long?, contact: Contact, chatModel: ChatModel) { when { - contact.activeConn == null && contact.profile.contactLink != null -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, contact, close = null, openChat = true) - else -> withBGApi { openChat(ChatInfo.Direct(contact), chatModel) } + contact.activeConn == null && contact.profile.contactLink != null -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true) + else -> withBGApi { openChat(rhId, ChatInfo.Direct(contact), chatModel) } } } -fun groupChatAction(groupInfo: GroupInfo, chatModel: ChatModel, inProgress: MutableState? = null) { +fun groupChatAction(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, inProgress: MutableState? = null) { when (groupInfo.membership.memberStatus) { - GroupMemberStatus.MemInvited -> acceptGroupInvitationAlertDialog(groupInfo, chatModel, inProgress) + GroupMemberStatus.MemInvited -> acceptGroupInvitationAlertDialog(rhId, groupInfo, chatModel, inProgress) GroupMemberStatus.MemAccepted -> groupInvitationAcceptedAlert() - else -> withBGApi { openChat(ChatInfo.Group(groupInfo), chatModel) } + else -> withBGApi { openChat(rhId, ChatInfo.Group(groupInfo), chatModel) } } } -suspend fun openDirectChat(contactId: Long, chatModel: ChatModel) { - val chat = chatModel.controller.apiGetChat(ChatType.Direct, contactId) +suspend fun openDirectChat(rhId: Long?, contactId: Long, chatModel: ChatModel) { + val chat = chatModel.controller.apiGetChat(rhId, ChatType.Direct, contactId) if (chat != null) { openLoadedChat(chat, chatModel) } } -suspend fun openGroupChat(groupId: Long, chatModel: ChatModel) { - val chat = chatModel.controller.apiGetChat(ChatType.Group, groupId) +suspend fun openGroupChat(rhId: Long?, groupId: Long, chatModel: ChatModel) { + val chat = chatModel.controller.apiGetChat(rhId, ChatType.Group, groupId) if (chat != null) { openLoadedChat(chat, chatModel) } } -suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) { +suspend fun openChat(rhId: Long?, chatInfo: ChatInfo, chatModel: ChatModel) { Log.d(TAG, "TODOCHAT: openChat: opening ${chatInfo.id}, current chatId ${ChatModel.chatId.value}, size ${ChatModel.chatItems.size}") - val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId) + val chat = chatModel.controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId) if (chat != null) { openLoadedChat(chat, chatModel) Log.d(TAG, "TODOCHAT: openChat: opened ${chatInfo.id}, current chatId ${ChatModel.chatId.value}, size ${ChatModel.chatItems.size}") @@ -164,22 +164,24 @@ fun openLoadedChat(chat: Chat, chatModel: ChatModel) { chatModel.chatId.value = chat.chatInfo.id } -suspend fun apiLoadPrevMessages(chatInfo: ChatInfo, chatModel: ChatModel, beforeChatItemId: Long, search: String) { +suspend fun apiLoadPrevMessages(ch: Chat, chatModel: ChatModel, beforeChatItemId: Long, search: String) { + val chatInfo = ch.chatInfo val pagination = ChatPagination.Before(beforeChatItemId, ChatPagination.PRELOAD_COUNT) - val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId, pagination, search) ?: return + val chat = chatModel.controller.apiGetChat(ch.remoteHostId, chatInfo.chatType, chatInfo.apiId, pagination, search) ?: return if (chatModel.chatId.value != chat.id) return chatModel.chatItems.addAll(0, chat.chatItems) } -suspend fun apiFindMessages(chatInfo: ChatInfo, chatModel: ChatModel, search: String) { - val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId, search = search) ?: return +suspend fun apiFindMessages(ch: Chat, chatModel: ChatModel, search: String) { + val chatInfo = ch.chatInfo + val chat = chatModel.controller.apiGetChat(ch.remoteHostId, chatInfo.chatType, chatInfo.apiId, search = search) ?: return if (chatModel.chatId.value != chat.id) return chatModel.chatItems.clear() chatModel.chatItems.addAll(0, chat.chatItems) } -suspend fun setGroupMembers(groupInfo: GroupInfo, chatModel: ChatModel) { - val groupMembers = chatModel.controller.apiListMembers(groupInfo.groupId) +suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) { + val groupMembers = chatModel.controller.apiListMembers(rhId, groupInfo.groupId) val currentMembers = chatModel.groupMembers val newMembers = groupMembers.map { newMember -> val currentMember = currentMembers.find { it.id == newMember.id } @@ -230,7 +232,7 @@ fun GroupMenuItems( } GroupMemberStatus.MemAccepted -> { if (groupInfo.membership.memberCurrent) { - LeaveGroupAction(groupInfo, chatModel, showMenu) + LeaveGroupAction(chat.remoteHostId, groupInfo, chatModel, showMenu) } if (groupInfo.canDelete) { DeleteGroupAction(chat, groupInfo, chatModel, showMenu) @@ -246,7 +248,7 @@ fun GroupMenuItems( ToggleNotificationsChatAction(chat, chatModel, chat.chatInfo.ntfsEnabled, showMenu) ClearChatAction(chat, chatModel, showMenu) if (groupInfo.membership.memberCurrent) { - LeaveGroupAction(groupInfo, chatModel, showMenu) + LeaveGroupAction(chat.remoteHostId, groupInfo, chatModel, showMenu) } if (groupInfo.canDelete) { DeleteGroupAction(chat, groupInfo, chatModel, showMenu) @@ -310,7 +312,7 @@ fun ClearChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState Unit = { withApi { inProgress.value = true - chatModel.controller.apiJoinGroup(groupInfo.groupId) + chatModel.controller.apiJoinGroup(chat.remoteHostId, groupInfo.groupId) inProgress.value = false } } @@ -370,12 +372,12 @@ fun JoinGroupAction( } @Composable -fun LeaveGroupAction(groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState) { +fun LeaveGroupAction(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState) { ItemAction( stringResource(MR.strings.leave_group_button), painterResource(MR.images.ic_logout), onClick = { - leaveGroupDialog(groupInfo, chatModel) + leaveGroupDialog(rhId, groupInfo, chatModel) showMenu.value = false }, color = Color.Red @@ -383,13 +385,13 @@ fun LeaveGroupAction(groupInfo: GroupInfo, chatModel: ChatModel, showMenu: Mutab } @Composable -fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatModel, showMenu: MutableState) { +fun ContactRequestMenuItems(rhId: Long?, chatInfo: ChatInfo.ContactRequest, chatModel: ChatModel, showMenu: MutableState) { ItemAction( stringResource(MR.strings.accept_contact_button), painterResource(MR.images.ic_check), color = MaterialTheme.colors.onBackground, onClick = { - acceptContactRequest(incognito = false, chatInfo.apiId, chatInfo, true, chatModel) + acceptContactRequest(rhId, incognito = false, chatInfo.apiId, chatInfo, true, chatModel) showMenu.value = false } ) @@ -398,7 +400,7 @@ fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatMo painterResource(MR.images.ic_theater_comedy), color = MaterialTheme.colors.onBackground, onClick = { - acceptContactRequest(incognito = true, chatInfo.apiId, chatInfo, true, chatModel) + acceptContactRequest(rhId, incognito = true, chatInfo.apiId, chatInfo, true, chatModel) showMenu.value = false } ) @@ -406,7 +408,7 @@ fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatMo stringResource(MR.strings.reject_contact_button), painterResource(MR.images.ic_close), onClick = { - rejectContactRequest(chatInfo, chatModel) + rejectContactRequest(rhId, chatInfo, chatModel) showMenu.value = false }, color = Color.Red @@ -414,7 +416,7 @@ fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatMo } @Composable -fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel: ChatModel, showMenu: MutableState) { +fun ContactConnectionMenuItems(rhId: Long?, chatInfo: ChatInfo.ContactConnection, chatModel: ChatModel, showMenu: MutableState) { ItemAction( stringResource(MR.strings.set_contact_name), painterResource(MR.images.ic_edit), @@ -422,7 +424,7 @@ fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel: ModalManager.center.closeModals() ModalManager.end.closeModals() ModalManager.center.showModalCloseable(true, showClose = appPlatform.isAndroid) { close -> - ContactConnectionInfoView(chatModel, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, true, close) + ContactConnectionInfoView(chatModel, rhId, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, true, close) } showMenu.value = false }, @@ -431,7 +433,7 @@ fun ContactConnectionMenuItems(chatInfo: ChatInfo.ContactConnection, chatModel: stringResource(MR.strings.delete_verb), painterResource(MR.images.ic_delete), onClick = { - deleteContactConnectionAlert(chatInfo.contactConnection, chatModel) { + deleteContactConnectionAlert(rhId, chatInfo.contactConnection, chatModel) { if (chatModel.chatId.value == null) { ModalManager.center.closeModals() ModalManager.end.closeModals() @@ -471,8 +473,9 @@ fun markChatRead(c: Chat, chatModel: ChatModel) { withApi { if (chat.chatStats.unreadCount > 0) { val minUnreadItemId = chat.chatStats.minUnreadItemId - chatModel.markChatItemsRead(chat.chatInfo) + chatModel.markChatItemsRead(chat) chatModel.controller.apiChatRead( + chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId, CC.ItemRange(minUnreadItemId, chat.chatItems.last().id) @@ -481,12 +484,13 @@ fun markChatRead(c: Chat, chatModel: ChatModel) { } if (chat.chatStats.unreadChat) { val success = chatModel.controller.apiChatUnread( + chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId, false ) if (success) { - chatModel.replaceChat(chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false))) + chatModel.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false))) } } } @@ -498,17 +502,18 @@ fun markChatUnread(chat: Chat, chatModel: ChatModel) { withApi { val success = chatModel.controller.apiChatUnread( + chat.remoteHostId, chat.chatInfo.chatType, chat.chatInfo.apiId, true ) if (success) { - chatModel.replaceChat(chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true))) + chatModel.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true))) } } } -fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) { +fun contactRequestAlertDialog(rhId: Long?, contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) { AlertManager.shared.showAlertDialogButtonsColumn( title = generalGetString(MR.strings.accept_connection_request__question), text = AnnotatedString(generalGetString(MR.strings.if_you_choose_to_reject_the_sender_will_not_be_notified)), @@ -516,19 +521,19 @@ fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel Column { SectionItemView({ AlertManager.shared.hideAlert() - acceptContactRequest(incognito = false, contactRequest.apiId, contactRequest, true, chatModel) + acceptContactRequest(rhId, incognito = false, contactRequest.apiId, contactRequest, true, chatModel) }) { Text(generalGetString(MR.strings.accept_contact_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } SectionItemView({ AlertManager.shared.hideAlert() - acceptContactRequest(incognito = true, contactRequest.apiId, contactRequest, true, chatModel) + acceptContactRequest(rhId, incognito = true, contactRequest.apiId, contactRequest, true, chatModel) }) { Text(generalGetString(MR.strings.accept_contact_incognito_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } SectionItemView({ AlertManager.shared.hideAlert() - rejectContactRequest(contactRequest, chatModel) + rejectContactRequest(rhId, contactRequest, chatModel) }) { Text(generalGetString(MR.strings.reject_contact_button), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) } @@ -537,24 +542,24 @@ fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel ) } -fun acceptContactRequest(incognito: Boolean, apiId: Long, contactRequest: ChatInfo.ContactRequest?, isCurrentUser: Boolean, chatModel: ChatModel) { +fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRequest: ChatInfo.ContactRequest?, isCurrentUser: Boolean, chatModel: ChatModel) { withApi { - val contact = chatModel.controller.apiAcceptContactRequest(incognito, apiId) + val contact = chatModel.controller.apiAcceptContactRequest(rhId, incognito, apiId) if (contact != null && isCurrentUser && contactRequest != null) { - val chat = Chat(ChatInfo.Direct(contact), listOf()) - chatModel.replaceChat(contactRequest.id, chat) + val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf()) + chatModel.replaceChat(rhId, contactRequest.id, chat) } } } -fun rejectContactRequest(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) { +fun rejectContactRequest(rhId: Long?, contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) { withApi { - chatModel.controller.apiRejectContactRequest(contactRequest.apiId) - chatModel.removeChat(contactRequest.id) + chatModel.controller.apiRejectContactRequest(rhId, contactRequest.apiId) + chatModel.removeChat(rhId, contactRequest.id) } } -fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel: ChatModel, onSuccess: () -> Unit) { +fun deleteContactConnectionAlert(rhId: Long?, connection: PendingContactConnection, chatModel: ChatModel, onSuccess: () -> Unit) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.delete_pending_connection__question), text = generalGetString( @@ -565,8 +570,8 @@ fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel onConfirm = { withApi { AlertManager.shared.hideAlert() - if (chatModel.controller.apiDeleteChat(ChatType.ContactConnection, connection.apiId)) { - chatModel.removeChat(connection.id) + if (chatModel.controller.apiDeleteChat(rhId, ChatType.ContactConnection, connection.apiId)) { + chatModel.removeChat(rhId, connection.id) onSuccess() } } @@ -575,16 +580,17 @@ fun deleteContactConnectionAlert(connection: PendingContactConnection, chatModel ) } -fun pendingContactAlertDialog(chatInfo: ChatInfo, chatModel: ChatModel) { +// TODO why is it not used +fun pendingContactAlertDialog(rhId: Long?, chatInfo: ChatInfo, chatModel: ChatModel) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.alert_title_contact_connection_pending), text = generalGetString(MR.strings.alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry), confirmText = generalGetString(MR.strings.button_delete_contact), onConfirm = { withApi { - val r = chatModel.controller.apiDeleteChat(chatInfo.chatType, chatInfo.apiId) + val r = chatModel.controller.apiDeleteChat(rhId, chatInfo.chatType, chatInfo.apiId) if (r) { - chatModel.removeChat(chatInfo.id) + chatModel.removeChat(rhId, chatInfo.id) if (chatModel.chatId.value == chatInfo.id) { chatModel.chatId.value = null ModalManager.end.closeModals() @@ -599,6 +605,7 @@ fun pendingContactAlertDialog(chatInfo: ChatInfo, chatModel: ChatModel) { fun askCurrentOrIncognitoProfileConnectContactViaAddress( chatModel: ChatModel, + rhId: Long?, contact: Contact, close: (() -> Unit)?, openChat: Boolean @@ -611,9 +618,9 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress( AlertManager.shared.hideAlert() withApi { close?.invoke() - val ok = connectContactViaAddress(chatModel, contact.contactId, incognito = false) + val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = false) if (ok && openChat) { - openDirectChat(contact.contactId, chatModel) + openDirectChat(rhId, contact.contactId, chatModel) } } }) { @@ -623,9 +630,9 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress( AlertManager.shared.hideAlert() withApi { close?.invoke() - val ok = connectContactViaAddress(chatModel, contact.contactId, incognito = true) + val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = true) if (ok && openChat) { - openDirectChat(contact.contactId, chatModel) + openDirectChat(rhId, contact.contactId, chatModel) } } }) { @@ -641,10 +648,10 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress( ) } -suspend fun connectContactViaAddress(chatModel: ChatModel, contactId: Long, incognito: Boolean): Boolean { - val contact = chatModel.controller.apiConnectContactViaAddress(incognito, contactId) +suspend fun connectContactViaAddress(chatModel: ChatModel, rhId: Long?, contactId: Long, incognito: Boolean): Boolean { + val contact = chatModel.controller.apiConnectContactViaAddress(rhId, incognito, contactId) if (contact != null) { - chatModel.updateContact(contact) + chatModel.updateContact(rhId, contact) AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.connection_request_sent), text = generalGetString(MR.strings.you_will_be_connected_when_your_connection_request_is_accepted) @@ -654,7 +661,7 @@ suspend fun connectContactViaAddress(chatModel: ChatModel, contactId: Long, inco return false } -fun acceptGroupInvitationAlertDialog(groupInfo: GroupInfo, chatModel: ChatModel, inProgress: MutableState? = null) { +fun acceptGroupInvitationAlertDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, inProgress: MutableState? = null) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.join_group_question), text = generalGetString(MR.strings.you_are_invited_to_group_join_to_connect_with_group_members), @@ -662,12 +669,12 @@ fun acceptGroupInvitationAlertDialog(groupInfo: GroupInfo, chatModel: ChatModel, onConfirm = { withApi { inProgress?.value = true - chatModel.controller.apiJoinGroup(groupInfo.groupId) + chatModel.controller.apiJoinGroup(rhId, groupInfo.groupId) inProgress?.value = false } }, dismissText = generalGetString(MR.strings.delete_verb), - onDismiss = { deleteGroup(groupInfo, chatModel) } + onDismiss = { deleteGroup(rhId, groupInfo, chatModel) } ) } @@ -679,11 +686,11 @@ fun cantInviteIncognitoAlert() { ) } -fun deleteGroup(groupInfo: GroupInfo, chatModel: ChatModel) { +fun deleteGroup(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) { withApi { - val r = chatModel.controller.apiDeleteChat(ChatType.Group, groupInfo.apiId) + val r = chatModel.controller.apiDeleteChat(rhId, ChatType.Group, groupInfo.apiId) if (r) { - chatModel.removeChat(groupInfo.id) + chatModel.removeChat(rhId, groupInfo.id) if (chatModel.chatId.value == groupInfo.id) { chatModel.chatId.value = null ModalManager.end.closeModals() @@ -723,15 +730,15 @@ fun updateChatSettings(chat: Chat, chatSettings: ChatSettings, chatModel: ChatMo withApi { val res = when (newChatInfo) { is ChatInfo.Direct -> with(newChatInfo) { - chatModel.controller.apiSetSettings(chatType, apiId, contact.chatSettings) + chatModel.controller.apiSetSettings(chat.remoteHostId, chatType, apiId, contact.chatSettings) } is ChatInfo.Group -> with(newChatInfo) { - chatModel.controller.apiSetSettings(chatType, apiId, groupInfo.chatSettings) + chatModel.controller.apiSetSettings(chat.remoteHostId, chatType, apiId, groupInfo.chatSettings) } else -> false } if (res && newChatInfo != null) { - chatModel.updateChatInfo(newChatInfo) + chatModel.updateChatInfo(chat.remoteHostId, newChatInfo) if (chatSettings.enableNtfs != MsgFilter.All) { ntfManager.cancelNotificationsForChat(chat.id) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 7883a7a738..1644d286f5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -53,7 +53,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf val url = chatModel.appOpenUrl.value if (url != null) { chatModel.appOpenUrl.value = null - connectIfOpenedViaUri(url, chatModel) + connectIfOpenedViaUri(chatModel.remoteHostId, url, chatModel) } } if (appPlatform.isDesktop) { @@ -117,7 +117,8 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf } if (searchInList.isEmpty()) { DesktopActiveCallOverlayLayout(newChatSheetState) - NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet) + // TODO disable this button and sheet for the duration of the switch + NewChatSheet(chatModel, chatModel.remoteHostId, newChatSheetState, stopped, hideNewChatSheet) } if (appPlatform.isAndroid) { UserPicker(chatModel, userPickerState, switchingUsersAndHosts) { @@ -317,13 +318,13 @@ private fun ProgressIndicator() { @Composable expect fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow) -fun connectIfOpenedViaUri(uri: URI, chatModel: ChatModel) { +fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) { Log.d(TAG, "connectIfOpenedViaUri: opened via link") if (chatModel.currentUser.value == null) { chatModel.appOpenUrl.value = uri } else { withApi { - planAndConnect(chatModel, uri, incognito = null, close = null) + planAndConnect(chatModel, rhId, uri, incognito = null, close = null) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt index e423a591da..ad8f93990f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListNavLinkView.kt @@ -20,13 +20,13 @@ fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) { is ChatInfo.Direct -> ShareListNavLinkLayout( chatLinkPreview = { SharePreviewView(chat) }, - click = { directChatAction(chat.chatInfo.contact, chatModel) }, + click = { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) }, stopped ) is ChatInfo.Group -> ShareListNavLinkLayout( chatLinkPreview = { SharePreviewView(chat) }, - click = { groupChatAction(chat.chatInfo.groupInfo, chatModel) }, + click = { groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel) }, stopped ) is ChatInfo.ContactRequest, is ChatInfo.ContactConnection, is ChatInfo.InvalidJSON -> {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index caf8ec5cb0..20e856df6e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -84,7 +84,7 @@ fun UserPicker( .filter { it } .collect { try { - val updatedUsers = chatModel.controller.listUsers().sortedByDescending { it.user.activeUser } + val updatedUsers = chatModel.controller.listUsers(chatModel.remoteHostId).sortedByDescending { it.user.activeUser } var same = users.size == updatedUsers.size if (same) { for (i in 0 until minOf(users.size, updatedUsers.size)) { @@ -129,7 +129,7 @@ fun UserPicker( switchingUsersAndHosts.value = true } ModalManager.closeAllModalsEverywhere() - chatModel.controller.changeActiveUser(u.user.userId, null) + chatModel.controller.changeActiveUser(u.user.remoteHostId, u.user.userId, null) job.cancel() switchingUsersAndHosts.value = false } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 0cca354746..1f4be29664 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -65,6 +65,8 @@ fun DatabaseView( Box( Modifier.fillMaxSize(), ) { + val user = m.currentUser.value + val rhId = user?.remoteHostId DatabaseLayout( progressIndicator.value, remember { m.chatRunning }.value != false, @@ -80,7 +82,7 @@ fun DatabaseView( chatLastStart, appFilesCountAndSize, chatItemTTL, - m.currentUser.value, + user, m.users, startChat = { startChat(m, chatLastStart, m.chatDbChanged) }, stopChatAlert = { stopChatAlert(m) }, @@ -91,9 +93,9 @@ fun DatabaseView( val oldValue = chatItemTTL.value chatItemTTL.value = it if (it < oldValue) { - setChatItemTTLAlert(m, chatItemTTL, progressIndicator, appFilesCountAndSize) + setChatItemTTLAlert(m, rhId, chatItemTTL, progressIndicator, appFilesCountAndSize) } else if (it != oldValue) { - setCiTTL(m, chatItemTTL, progressIndicator, appFilesCountAndSize) + setCiTTL(m, rhId, chatItemTTL, progressIndicator, appFilesCountAndSize) } }, showSettingsModal @@ -265,7 +267,7 @@ fun DatabaseLayout( } private fun setChatItemTTLAlert( - m: ChatModel, selectedChatItemTTL: MutableState, + m: ChatModel, rhId: Long?, selectedChatItemTTL: MutableState, progressIndicator: MutableState, appFilesCountAndSize: MutableState>, ) { @@ -273,7 +275,7 @@ private fun setChatItemTTLAlert( title = generalGetString(MR.strings.enable_automatic_deletion_question), text = generalGetString(MR.strings.enable_automatic_deletion_message), confirmText = generalGetString(MR.strings.delete_messages), - onConfirm = { setCiTTL(m, selectedChatItemTTL, progressIndicator, appFilesCountAndSize) }, + onConfirm = { setCiTTL(m, rhId, selectedChatItemTTL, progressIndicator, appFilesCountAndSize) }, onDismiss = { selectedChatItemTTL.value = m.chatItemTTL.value }, destructive = true, ) @@ -592,6 +594,7 @@ private fun deleteChat(m: ChatModel, progressIndicator: MutableState) { private fun setCiTTL( m: ChatModel, + rhId: Long?, chatItemTTL: MutableState, progressIndicator: MutableState, appFilesCountAndSize: MutableState>, @@ -600,7 +603,7 @@ private fun setCiTTL( progressIndicator.value = true withApi { try { - m.controller.setChatItemTTL(chatItemTTL.value) + m.controller.setChatItemTTL(rhId, chatItemTTL.value) // Update model on success m.chatItemTTL.value = chatItemTTL.value afterSetCiTTL(m, progressIndicator, appFilesCountAndSize) @@ -623,7 +626,8 @@ private fun afterSetCiTTL( withApi { try { updatingChatsMutex.withLock { - val chats = m.controller.apiGetChats() + // this is using current remote host on purpose - if it changes during update, it will load correct chats + val chats = m.controller.apiGetChats(m.remoteHostId) m.updateChats(chats) } } catch (e: Exception) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 4ed5ea56b4..dbadec32f1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -373,7 +373,7 @@ inline fun serializableSaver(): Saver = Saver( fun UriHandler.openVerifiedSimplexUri(uri: String) { val URI = try { URI.create(uri) } catch (e: Exception) { null } if (URI != null) { - connectIfOpenedViaUri(URI, ChatModel) + connectIfOpenedViaUri(chatModel.remoteHostId, URI, ChatModel) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt index 8b5c2a8336..c64c3dd29a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt @@ -63,7 +63,7 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: ( if (!displayName.isNullOrEmpty()) { profile = Profile(displayName = displayName, fullName = "") } - val createdUser = m.controller.apiCreateActiveUser(profile, pastTimestamp = true) + val createdUser = m.controller.apiCreateActiveUser(null, profile, pastTimestamp = true) m.currentUser.value = createdUser m.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete) if (createdUser != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt index ef3633d1fa..360667fcfb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactView.kt @@ -25,11 +25,13 @@ import chat.simplex.res.MR @Composable fun AddContactView( chatModel: ChatModel, + rhId: Long?, connReqInvitation: String, contactConnection: MutableState ) { val clipboard = LocalClipboardManager.current AddContactLayout( + rhId = rhId, chatModel = chatModel, incognitoPref = chatModel.controller.appPrefs.incognito, connReq = connReqInvitation, @@ -52,6 +54,7 @@ fun AddContactView( @Composable fun AddContactLayout( chatModel: ChatModel, + rhId: Long?, incognitoPref: SharedPreference, connReq: String, contactConnection: MutableState, @@ -63,9 +66,9 @@ fun AddContactLayout( withApi { val contactConnVal = contactConnection.value if (contactConnVal != null) { - chatModel.controller.apiSetConnectionIncognito(contactConnVal.pccConnId, incognito.value)?.let { + chatModel.controller.apiSetConnectionIncognito(rhId, contactConnVal.pccConnId, incognito.value)?.let { contactConnection.value = it - chatModel.updateContactConnection(it) + chatModel.updateContactConnection(rhId, it) } } } @@ -172,6 +175,7 @@ fun sharedProfileInfo( fun PreviewAddContactView() { SimpleXTheme { AddContactLayout( + rhId = null, chatModel = ChatModel, incognitoPref = SharedPreference({ false }, {}), connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index 9b2cedefaa..d60ee75311 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -32,25 +32,25 @@ import kotlinx.coroutines.launch import java.net.URI @Composable -fun AddGroupView(chatModel: ChatModel, close: () -> Unit) { +fun AddGroupView(chatModel: ChatModel, rhId: Long?, close: () -> Unit) { AddGroupLayout( createGroup = { incognito, groupProfile -> withApi { - val groupInfo = chatModel.controller.apiNewGroup(incognito, groupProfile) + val groupInfo = chatModel.controller.apiNewGroup(rhId, incognito, groupProfile) if (groupInfo != null) { - chatModel.addChat(Chat(chatInfo = ChatInfo.Group(groupInfo), chatItems = listOf())) + chatModel.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Group(groupInfo), chatItems = listOf())) chatModel.chatItems.clear() chatModel.chatItemStatuses.clear() chatModel.chatId.value = groupInfo.id - setGroupMembers(groupInfo, chatModel) + setGroupMembers(rhId, groupInfo, chatModel) close.invoke() if (!groupInfo.incognito) { ModalManager.end.showModalCloseable(true) { close -> - AddGroupMembersView(groupInfo, creatingGroup = true, chatModel, close) + AddGroupMembersView(rhId, groupInfo, creatingGroup = true, chatModel, close) } } else { ModalManager.end.showModalCloseable(true) { close -> - GroupLinkView(chatModel, groupInfo, connReqContact = null, memberRole = null, onGroupLinkUpdated = null, creatingGroup = true, close) + GroupLinkView(chatModel, rhId, groupInfo, connReqContact = null, memberRole = null, onGroupLinkUpdated = null, creatingGroup = true, close) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt index a2bc2c4df4..e41c8701e3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectViaLinkView.kt @@ -8,4 +8,4 @@ enum class ConnectViaLinkTab { } @Composable -expect fun ConnectViaLinkView(m: ChatModel, close: () -> Unit) +expect fun ConnectViaLinkView(m: ChatModel, rhId: Long?, close: () -> Unit) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index d04a85d905..50419cdaff 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -30,6 +30,7 @@ import chat.simplex.res.MR @Composable fun ContactConnectionInfoView( chatModel: ChatModel, + rhId: Long?, connReqInvitation: String?, contactConnection: PendingContactConnection, focusAlias: Boolean, @@ -55,8 +56,8 @@ fun ContactConnectionInfoView( connReq = connReqInvitation, contactConnection = contactConnection, focusAlias = focusAlias, - deleteConnection = { deleteContactConnectionAlert(contactConnection, chatModel, close) }, - onLocalAliasChanged = { setContactAlias(contactConnection, it, chatModel) }, + deleteConnection = { deleteContactConnectionAlert(rhId, contactConnection, chatModel, close) }, + onLocalAliasChanged = { setContactAlias(rhId, contactConnection, it, chatModel) }, share = { if (connReqInvitation != null) clipboard.shareText(connReqInvitation) }, learnMore = { ModalManager.center.showModal { @@ -165,9 +166,9 @@ fun DeleteButton(onClick: () -> Unit) { ) } -private fun setContactAlias(contactConnection: PendingContactConnection, localAlias: String, chatModel: ChatModel) = withApi { - chatModel.controller.apiSetConnectionAlias(contactConnection.pccConnId, localAlias)?.let { - chatModel.updateContactConnection(it) +private fun setContactAlias(rhId: Long?, contactConnection: PendingContactConnection, localAlias: String, chatModel: ChatModel) = withApi { + chatModel.controller.apiSetConnectionAlias(rhId, contactConnection.pccConnId, localAlias)?.let { + chatModel.updateContactConnection(rhId, it) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt index 94538655b9..f4252f53b0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/CreateLinkView.kt @@ -20,7 +20,7 @@ enum class CreateLinkTab { } @Composable -fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) { +fun CreateLinkView(m: ChatModel, rhId: Long?, initialSelection: CreateLinkTab) { val selection = remember { mutableStateOf(initialSelection) } val connReqInvitation = rememberSaveable { m.connReqInv } val contactConnection: MutableState = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(null) } @@ -32,7 +32,7 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) { && contactConnection.value == null && !creatingConnReq.value ) { - createInvitation(m, creatingConnReq, connReqInvitation, contactConnection) + createInvitation(m, rhId, creatingConnReq, connReqInvitation, contactConnection) } } /** When [AddContactView] is open, we don't need to drop [chatModel.connReqInv]. @@ -65,10 +65,10 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) { Column(Modifier.weight(1f)) { when (selection.value) { CreateLinkTab.ONE_TIME -> { - AddContactView(m, connReqInvitation.value ?: "", contactConnection) + AddContactView(m, rhId,connReqInvitation.value ?: "", contactConnection) } CreateLinkTab.LONG_TERM -> { - UserAddressView(m, viaCreateLinkView = true, close = {}) + UserAddressView(m, rhId, viaCreateLinkView = true, close = {}) } } } @@ -100,13 +100,14 @@ fun CreateLinkView(m: ChatModel, initialSelection: CreateLinkTab) { private fun createInvitation( m: ChatModel, + rhId: Long?, creatingConnReq: MutableState, connReqInvitation: MutableState, contactConnection: MutableState ) { creatingConnReq.value = true withApi { - val r = m.controller.apiAddContact(incognito = m.controller.appPrefs.incognito.get()) + val r = m.controller.apiAddContact(rhId, incognito = m.controller.appPrefs.incognito.get()) if (r != null) { connReqInvitation.value = r.first contactConnection.value = r.second diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 8ec54e344f..382bc72e4f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -33,7 +33,8 @@ import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable -fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow, stopped: Boolean, closeNewChatSheet: (animated: Boolean) -> Unit) { +fun NewChatSheet(chatModel: ChatModel, rhId: Long?, newChatSheetState: StateFlow, stopped: Boolean, closeNewChatSheet: (animated: Boolean) -> Unit) { + // TODO close new chat if remote host changes in model if (newChatSheetState.collectAsState().value.isVisible()) BackHandler { closeNewChatSheet(true) } NewChatSheetLayout( newChatSheetState, @@ -41,17 +42,17 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow ConnectViaLinkView(chatModel, close) } + ModalManager.center.showModalCloseable { close -> ConnectViaLinkView(chatModel, rhId, close) } }, createGroup = { closeNewChatSheet(false) ModalManager.center.closeModals() - ModalManager.center.showCustomModal { close -> AddGroupView(chatModel, close) } + ModalManager.center.showCustomModal { close -> AddGroupView(chatModel, rhId, close) } }, closeNewChatSheet, ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt index b142b8e16a..d40fa97624 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/PasteToConnect.kt @@ -24,11 +24,12 @@ import chat.simplex.res.MR import java.net.URI @Composable -fun PasteToConnectView(chatModel: ChatModel, close: () -> Unit) { +fun PasteToConnectView(chatModel: ChatModel, rhId: Long?, close: () -> Unit) { val connectionLink = remember { mutableStateOf("") } val clipboard = LocalClipboardManager.current PasteToConnectLayout( chatModel = chatModel, + rhId = rhId, incognitoPref = chatModel.controller.appPrefs.incognito, connectionLink = connectionLink, pasteFromClipboard = { @@ -41,6 +42,7 @@ fun PasteToConnectView(chatModel: ChatModel, close: () -> Unit) { @Composable fun PasteToConnectLayout( chatModel: ChatModel, + rhId: Long?, incognitoPref: SharedPreference, connectionLink: MutableState, pasteFromClipboard: () -> Unit, @@ -52,7 +54,7 @@ fun PasteToConnectLayout( try { val uri = URI(connReqUri) withApi { - planAndConnect(chatModel, uri, incognito = incognito.value, close) + planAndConnect(chatModel, rhId, uri, incognito = incognito.value, close) } } catch (e: RuntimeException) { AlertManager.shared.showAlertMsg( @@ -124,6 +126,7 @@ fun PreviewPasteToConnectTextbox() { SimpleXTheme { PasteToConnectLayout( chatModel = ChatModel, + rhId = null, incognitoPref = SharedPreference({ false }, {}), connectionLink = remember { mutableStateOf("") }, pasteFromClipboard = {}, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt index 7629256fc3..523b7e5327 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.kt @@ -26,7 +26,7 @@ import chat.simplex.res.MR import java.net.URI @Composable -expect fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) +expect fun ScanToConnectView(chatModel: ChatModel, rhId: Long?, close: () -> Unit) enum class ConnectionLinkType { INVITATION, CONTACT, GROUP @@ -34,21 +34,22 @@ enum class ConnectionLinkType { suspend fun planAndConnect( chatModel: ChatModel, + rhId: Long?, uri: URI, incognito: Boolean?, close: (() -> Unit)? ) { - val connectionPlan = chatModel.controller.apiConnectPlan(uri.toString()) + val connectionPlan = chatModel.controller.apiConnectPlan(rhId, uri.toString()) if (connectionPlan != null) { when (connectionPlan) { is ConnectionPlan.InvitationLink -> when (connectionPlan.invitationLinkPlan) { InvitationLinkPlan.Ok -> { Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito") if (incognito != null) { - connectViaUri(chatModel, uri, incognito, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } else { askCurrentOrIncognitoProfileAlert( - chatModel, uri, connectionPlan, close, + chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_via_invitation_link), text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)), connectDestructive = false @@ -62,12 +63,12 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link), confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } }, + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }, destructive = true, ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, uri, connectionPlan, close, + chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link)), connectDestructive = true @@ -78,7 +79,7 @@ suspend fun planAndConnect( Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting, incognito=$incognito") val contact = connectionPlan.invitationLinkPlan.contact_ if (contact != null) { - openKnownContact(chatModel, close, contact) + openKnownContact(chatModel, rhId, close, contact) AlertManager.shared.showAlertMsg( generalGetString(MR.strings.contact_already_exists), String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) @@ -93,7 +94,7 @@ suspend fun planAndConnect( is InvitationLinkPlan.Known -> { Log.d(TAG, "planAndConnect, .InvitationLink, .Known, incognito=$incognito") val contact = connectionPlan.invitationLinkPlan.contact - openKnownContact(chatModel, close, contact) + openKnownContact(chatModel, rhId, close, contact) AlertManager.shared.showAlertMsg( generalGetString(MR.strings.contact_already_exists), String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) @@ -104,10 +105,10 @@ suspend fun planAndConnect( ContactAddressPlan.Ok -> { Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito") if (incognito != null) { - connectViaUri(chatModel, uri, incognito, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } else { askCurrentOrIncognitoProfileAlert( - chatModel, uri, connectionPlan, close, + chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_via_contact_link), text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)), connectDestructive = false @@ -121,12 +122,12 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address), confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } }, + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }, destructive = true, ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, uri, connectionPlan, close, + chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address)), connectDestructive = true @@ -140,12 +141,12 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_repeat_connection_request), text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address), confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } }, + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }, destructive = true, ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, uri, connectionPlan, close, + chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_repeat_connection_request), text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address)), connectDestructive = true @@ -155,7 +156,7 @@ suspend fun planAndConnect( is ContactAddressPlan.ConnectingProhibit -> { Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit, incognito=$incognito") val contact = connectionPlan.contactAddressPlan.contact - openKnownContact(chatModel, close, contact) + openKnownContact(chatModel, rhId, close, contact) AlertManager.shared.showAlertMsg( generalGetString(MR.strings.contact_already_exists), String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) @@ -164,7 +165,7 @@ suspend fun planAndConnect( is ContactAddressPlan.Known -> { Log.d(TAG, "planAndConnect, .ContactAddress, .Known, incognito=$incognito") val contact = connectionPlan.contactAddressPlan.contact - openKnownContact(chatModel, close, contact) + openKnownContact(chatModel, rhId, close, contact) AlertManager.shared.showAlertMsg( generalGetString(MR.strings.contact_already_exists), String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) @@ -175,9 +176,9 @@ suspend fun planAndConnect( val contact = connectionPlan.contactAddressPlan.contact if (incognito != null) { close?.invoke() - connectContactViaAddress(chatModel, contact.contactId, incognito) + connectContactViaAddress(chatModel, rhId, contact.contactId, incognito) } else { - askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, contact, close, openChat = false) + askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close, openChat = false) } } } @@ -189,11 +190,11 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_via_group_link), text = generalGetString(MR.strings.you_will_join_group), confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } } + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } } ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, uri, connectionPlan, close, + chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_via_group_link), text = AnnotatedString(generalGetString(MR.strings.you_will_join_group)), connectDestructive = false @@ -203,7 +204,7 @@ suspend fun planAndConnect( is GroupLinkPlan.OwnLink -> { Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink, incognito=$incognito") val groupInfo = connectionPlan.groupLinkPlan.groupInfo - ownGroupLinkConfirmConnect(chatModel, uri, incognito, connectionPlan, groupInfo, close) + ownGroupLinkConfirmConnect(chatModel, rhId, uri, incognito, connectionPlan, groupInfo, close) } GroupLinkPlan.ConnectingConfirmReconnect -> { Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect, incognito=$incognito") @@ -212,12 +213,12 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_repeat_join_request), text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link), confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withApi { connectViaUri(chatModel, uri, incognito, connectionPlan, close) } }, + onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }, destructive = true, ) } else { askCurrentOrIncognitoProfileAlert( - chatModel, uri, connectionPlan, close, + chatModel, rhId, uri, connectionPlan, close, title = generalGetString(MR.strings.connect_plan_repeat_join_request), text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link)), connectDestructive = true @@ -242,7 +243,7 @@ suspend fun planAndConnect( is GroupLinkPlan.Known -> { Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito") val groupInfo = connectionPlan.groupLinkPlan.groupInfo - openKnownGroup(chatModel, close, groupInfo) + openKnownGroup(chatModel, rhId, close, groupInfo) AlertManager.shared.showAlertMsg( generalGetString(MR.strings.connect_plan_group_already_exists), String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName) @@ -253,10 +254,10 @@ suspend fun planAndConnect( } else { Log.d(TAG, "planAndConnect, plan error") if (incognito != null) { - connectViaUri(chatModel, uri, incognito, connectionPlan = null, close) + connectViaUri(chatModel, rhId, uri, incognito, connectionPlan = null, close) } else { askCurrentOrIncognitoProfileAlert( - chatModel, uri, connectionPlan = null, close, + chatModel, rhId, uri, connectionPlan = null, close, title = generalGetString(MR.strings.connect_plan_connect_via_link), connectDestructive = false ) @@ -266,12 +267,13 @@ suspend fun planAndConnect( suspend fun connectViaUri( chatModel: ChatModel, + rhId: Long?, uri: URI, incognito: Boolean, connectionPlan: ConnectionPlan?, close: (() -> Unit)? ): Boolean { - val r = chatModel.controller.apiConnect(incognito, uri.toString()) + val r = chatModel.controller.apiConnect(rhId, incognito, uri.toString()) val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION if (r) { close?.invoke() @@ -298,6 +300,7 @@ fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType fun askCurrentOrIncognitoProfileAlert( chatModel: ChatModel, + rhId: Long?, uri: URI, connectionPlan: ConnectionPlan?, close: (() -> Unit)?, @@ -314,7 +317,7 @@ fun askCurrentOrIncognitoProfileAlert( SectionItemView({ AlertManager.shared.hideAlert() withApi { - connectViaUri(chatModel, uri, incognito = false, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close) } }) { Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor) @@ -322,7 +325,7 @@ fun askCurrentOrIncognitoProfileAlert( SectionItemView({ AlertManager.shared.hideAlert() withApi { - connectViaUri(chatModel, uri, incognito = true, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close) } }) { Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor) @@ -337,18 +340,19 @@ fun askCurrentOrIncognitoProfileAlert( ) } -fun openKnownContact(chatModel: ChatModel, close: (() -> Unit)?, contact: Contact) { +fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact) { withApi { val c = chatModel.getContactChat(contact.contactId) if (c != null) { close?.invoke() - openDirectChat(contact.contactId, chatModel) + openDirectChat(rhId, contact.contactId, chatModel) } } } fun ownGroupLinkConfirmConnect( chatModel: ChatModel, + rhId: Long?, uri: URI, incognito: Boolean?, connectionPlan: ConnectionPlan?, @@ -363,7 +367,7 @@ fun ownGroupLinkConfirmConnect( // Open group SectionItemView({ AlertManager.shared.hideAlert() - openKnownGroup(chatModel, close, groupInfo) + openKnownGroup(chatModel, rhId, close, groupInfo) }) { Text(generalGetString(MR.strings.connect_plan_open_group), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } @@ -372,7 +376,7 @@ fun ownGroupLinkConfirmConnect( SectionItemView({ AlertManager.shared.hideAlert() withApi { - connectViaUri(chatModel, uri, incognito, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } }) { Text( @@ -385,7 +389,7 @@ fun ownGroupLinkConfirmConnect( SectionItemView({ AlertManager.shared.hideAlert() withApi { - connectViaUri(chatModel, uri, incognito = false, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close) } }) { Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) @@ -394,7 +398,7 @@ fun ownGroupLinkConfirmConnect( SectionItemView({ AlertManager.shared.hideAlert() withApi { - connectViaUri(chatModel, uri, incognito = true, connectionPlan, close) + connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close) } }) { Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) @@ -411,12 +415,12 @@ fun ownGroupLinkConfirmConnect( ) } -fun openKnownGroup(chatModel: ChatModel, close: (() -> Unit)?, groupInfo: GroupInfo) { +fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, groupInfo: GroupInfo) { withApi { val g = chatModel.getGroupChat(groupInfo.groupId) if (g != null) { close?.invoke() - openGroupChat(groupInfo.groupId, chatModel) + openGroupChat(rhId, groupInfo.groupId, chatModel) } } } @@ -424,6 +428,7 @@ fun openKnownGroup(chatModel: ChatModel, close: (() -> Unit)?, groupInfo: GroupI @Composable fun ConnectContactLayout( chatModel: ChatModel, + rhId: Long?, incognitoPref: SharedPreference, close: () -> Unit ) { @@ -435,7 +440,7 @@ fun ConnectContactLayout( try { val uri = URI(connReqUri) withApi { - planAndConnect(chatModel, uri, incognito = incognito.value, close) + planAndConnect(chatModel, rhId, uri, incognito = incognito.value, close) } } catch (e: RuntimeException) { AlertManager.shared.showAlertMsg( @@ -487,6 +492,7 @@ fun PreviewConnectContactLayout() { SimpleXTheme { ConnectContactLayout( chatModel = ChatModel, + rhId = null, incognitoPref = SharedPreference({ false }, {}), close = {}, ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt index 0132223383..49e62fb067 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt @@ -23,14 +23,14 @@ import chat.simplex.common.views.newchat.simplexChatLink import chat.simplex.res.MR @Composable -fun CreateSimpleXAddress(m: ChatModel) { +fun CreateSimpleXAddress(m: ChatModel, rhId: Long?) { var progressIndicator by remember { mutableStateOf(false) } val userAddress = remember { m.userAddress } val clipboard = LocalClipboardManager.current val uriHandler = LocalUriHandler.current LaunchedEffect(Unit) { - prepareChatBeforeAddressCreation() + prepareChatBeforeAddressCreation(rhId) } CreateSimpleXAddressLayout( @@ -45,11 +45,11 @@ fun CreateSimpleXAddress(m: ChatModel) { createAddress = { withApi { progressIndicator = true - val connReqContact = m.controller.apiCreateUserAddress() + val connReqContact = m.controller.apiCreateUserAddress(rhId) if (connReqContact != null) { m.userAddress.value = UserContactLinkRec(connReqContact) try { - val u = m.controller.apiSetProfileAddress(true) + val u = m.controller.apiSetProfileAddress(rhId, true) if (u != null) { m.updateUser(u) } @@ -176,18 +176,18 @@ private fun ProgressIndicator() { } } -private fun prepareChatBeforeAddressCreation() { +private fun prepareChatBeforeAddressCreation(rhId: Long?) { if (chatModel.users.isNotEmpty()) return withApi { - val user = chatModel.controller.apiGetActiveUser() ?: return@withApi + val user = chatModel.controller.apiGetActiveUser(rhId) ?: return@withApi chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { chatModel.controller.startChat(user) } else { - val users = chatModel.controller.listUsers() + val users = chatModel.controller.listUsers(rhId) chatModel.users.clear() chatModel.users.addAll(users) - chatModel.controller.getUserChatData() + chatModel.controller.getUserChatData(rhId) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index 9bc5ae846e..a4ebd23de4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -80,7 +80,7 @@ fun SetupDatabasePassphrase(m: ChatModel) { onDispose { if (m.chatRunning.value != true) { withBGApi { - val user = chatController.apiGetActiveUser() + val user = chatController.apiGetActiveUser(null) if (user != null) { m.controller.startChat(user) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt index 215899d0ff..f3496c850f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt @@ -34,7 +34,7 @@ fun HiddenProfileView( saveProfilePassword = { hidePassword -> withBGApi { try { - val u = m.controller.apiHideUser(user.userId, hidePassword) + val u = m.controller.apiHideUser(user, hidePassword) m.updateUser(u) close() } catch (e: Exception) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index f2fee926a7..9ee1b39384 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -168,9 +168,9 @@ fun NetworkAndServersView( ) { AppBarTitle(stringResource(MR.strings.network_and_servers)) SectionView(generalGetString(MR.strings.settings_section_title_messages)) { - SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.smp_servers), showCustomModal { m, close -> ProtocolServersView(m, ServerProtocol.SMP, close) }) + SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.smp_servers), showCustomModal { m, close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.SMP, close) }) - SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.xftp_servers), showCustomModal { m, close -> ProtocolServersView(m, ServerProtocol.XFTP, close) }) + SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.xftp_servers), showCustomModal { m, close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) }) UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showSettingsModal) UseOnionHosts(onionHosts, networkUseSocksProxy, showSettingsModal, useOnion) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt index 202602b287..e94c53f644 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt @@ -25,11 +25,11 @@ fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) { fun savePrefs(afterSave: () -> Unit = {}) { withApi { val newProfile = user.profile.toProfile().copy(preferences = preferences.toPreferences()) - val updated = m.controller.apiUpdateProfile(newProfile) + val updated = m.controller.apiUpdateProfile(user.remoteHostId, newProfile) if (updated != null) { val (updatedProfile, updatedContacts) = updated - m.updateCurrentUser(updatedProfile, preferences) - updatedContacts.forEach(m::updateContact) + m.updateCurrentUser(user.remoteHostId, updatedProfile, preferences) + updatedContacts.forEach { m.updateContact(user.remoteHostId, it) } currentPreferences = preferences } afterSave() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 84ab87c653..1a5aa49eb0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -99,7 +99,7 @@ fun PrivacySettingsView( fun setSendReceiptsContacts(enable: Boolean, clearOverrides: Boolean) { withApi { val mrs = UserMsgReceiptSettings(enable, clearOverrides) - chatModel.controller.apiSetUserContactReceipts(currentUser.userId, mrs) + chatModel.controller.apiSetUserContactReceipts(currentUser, mrs) chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) chatModel.currentUser.value = currentUser.copy(sendRcptsContacts = enable) if (clearOverrides) { @@ -111,7 +111,7 @@ fun PrivacySettingsView( val sendRcpts = contact.chatSettings.sendRcpts if (sendRcpts != null && sendRcpts != enable) { contact = contact.copy(chatSettings = contact.chatSettings.copy(sendRcpts = null)) - chatModel.updateContact(contact) + chatModel.updateContact(currentUser.remoteHostId, contact) } } } @@ -122,7 +122,7 @@ fun PrivacySettingsView( fun setSendReceiptsGroups(enable: Boolean, clearOverrides: Boolean) { withApi { val mrs = UserMsgReceiptSettings(enable, clearOverrides) - chatModel.controller.apiSetUserGroupReceipts(currentUser.userId, mrs) + chatModel.controller.apiSetUserGroupReceipts(currentUser, mrs) chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) chatModel.currentUser.value = currentUser.copy(sendRcptsSmallGroups = enable) if (clearOverrides) { @@ -134,7 +134,7 @@ fun PrivacySettingsView( val sendRcpts = groupInfo.chatSettings.sendRcpts if (sendRcpts != null && sendRcpts != enable) { groupInfo = groupInfo.copy(chatSettings = groupInfo.chatSettings.copy(sendRcpts = null)) - chatModel.updateGroup(groupInfo) + chatModel.updateGroup(currentUser.remoteHostId, groupInfo) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt index f3896a3de9..4e8da36a7e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt @@ -197,7 +197,7 @@ fun ShowTestStatus(server: ServerCfg, modifier: Modifier = Modifier) = suspend fun testServerConnection(server: ServerCfg, m: ChatModel): Pair = try { - val r = m.controller.testProtoServer(server.server) + val r = m.controller.testProtoServer(server.remoteHostId, server.server) server.copy(tested = r == null) to r } catch (e: Exception) { Log.e(TAG, "testServerConnection ${e.stackTraceToString()}") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt index 92246b72fb..cbcb7344f5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt @@ -28,7 +28,8 @@ import chat.simplex.res.MR import kotlinx.coroutines.launch @Composable -fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () -> Unit) { +fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtocol, close: () -> Unit) { + // TODO close if remote host changes var presetServers by remember { mutableStateOf(emptyList()) } var servers by remember { mutableStateOf(m.userSMPServersUnsaved.value ?: emptyList()) @@ -51,7 +52,7 @@ fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () } LaunchedEffect(Unit) { - val res = m.controller.getUserProtoServers(serverProtocol) + val res = m.controller.getUserProtoServers(rhId, serverProtocol) if (res != null) { currServers.value = res.protoServers presetServers = res.presetServers @@ -90,7 +91,7 @@ fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () ModalView( close = { if (saveDisabled.value) close() - else showUnsavedChangesAlert({ saveServers(serverProtocol, currServers, servers, m, close) }, close) + else showUnsavedChangesAlert({ saveServers(rhId, serverProtocol, currServers, servers, m, close) }, close) }, ) { ProtocolServersLayout( @@ -118,7 +119,7 @@ fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () SectionItemView({ AlertManager.shared.hideAlert() ModalManager.start.showModalCloseable { close -> - ScanProtocolServer { + ScanProtocolServer(rhId) { close() servers = servers + it m.userSMPServersUnsaved.value = servers @@ -133,7 +134,7 @@ fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () if (!hasAllPresets) { SectionItemView({ AlertManager.shared.hideAlert() - servers = (servers + addAllPresets(presetServers, servers, m)).sortedByDescending { it.preset } + servers = (servers + addAllPresets(rhId, presetServers, servers, m)).sortedByDescending { it.preset } }) { Text(stringResource(MR.strings.smp_servers_preset_add), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } @@ -155,7 +156,7 @@ fun ProtocolServersView(m: ChatModel, serverProtocol: ServerProtocol, close: () m.userSMPServersUnsaved.value = null }, saveSMPServers = { - saveServers(serverProtocol, currServers, servers, m) + saveServers(rhId, serverProtocol, currServers, servers, m) }, showServer = ::showServer, ) @@ -289,11 +290,11 @@ private fun uniqueAddress(s: ServerCfg, address: ServerAddress, servers: List, servers: List, m: ChatModel): Boolean = presetServers.all { hasPreset(it, servers) } ?: true -private fun addAllPresets(presetServers: List, servers: List, m: ChatModel): List { +private fun addAllPresets(rhId: Long?, presetServers: List, servers: List, m: ChatModel): List { val toAdd = ArrayList() for (srv in presetServers) { if (!hasPreset(srv, servers)) { - toAdd.add(ServerCfg(srv, preset = true, tested = null, enabled = true)) + toAdd.add(ServerCfg(remoteHostId = rhId, srv, preset = true, tested = null, enabled = true)) } } return toAdd @@ -346,9 +347,9 @@ private suspend fun runServersTest(servers: List, m: ChatModel, onUpd return fs } -private fun saveServers(protocol: ServerProtocol, currServers: MutableState>, servers: List, m: ChatModel, afterSave: () -> Unit = {}) { +private fun saveServers(rhId: Long?, protocol: ServerProtocol, currServers: MutableState>, servers: List, m: ChatModel, afterSave: () -> Unit = {}) { withApi { - if (m.controller.setUserProtoServers(protocol, servers)) { + if (m.controller.setUserProtoServers(rhId, protocol, servers)) { currServers.value = servers m.userSMPServersUnsaved.value = null } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt index 02582ec93c..ac74bd04d8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt @@ -13,10 +13,10 @@ import chat.simplex.common.views.newchat.QRCodeScanner import chat.simplex.res.MR @Composable -expect fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) +expect fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) @Composable -fun ScanProtocolServerLayout(onNext: (ServerCfg) -> Unit) { +fun ScanProtocolServerLayout(rhId: Long?, onNext: (ServerCfg) -> Unit) { Column( Modifier .fillMaxSize() @@ -32,7 +32,7 @@ fun ScanProtocolServerLayout(onNext: (ServerCfg) -> Unit) { QRCodeScanner { text -> val res = parseServerAddress(text) if (res != null) { - onNext(ServerCfg(text, false, null, true)) + onNext(ServerCfg(remoteHostId = rhId, text, false, null, true)) } else { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.smp_servers_invalid_address), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt index 089ec7713f..b75f522686 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt @@ -26,12 +26,12 @@ fun SetDeliveryReceiptsView(m: ChatModel) { if (currentUser != null) { withApi { try { - m.controller.apiSetAllContactReceipts(enable = true) + m.controller.apiSetAllContactReceipts(currentUser.remoteHostId, enable = true) m.currentUser.value = currentUser.copy(sendRcptsContacts = true) m.setDeliveryReceipts.value = false m.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) try { - val users = m.controller.listUsers() + val users = m.controller.listUsers(currentUser.remoteHostId) m.users.clear() m.users.addAll(users) } catch (e: Exception) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 2d4e5c86b9..7bd060e8d6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -155,7 +155,7 @@ fun SettingsLayout( } val profileHidden = rememberSaveable { mutableStateOf(false) } SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden) } } }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, it.currentUser.value?.remoteHostId, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true) ChatPreferencesItem(showCustomModal, stopped = stopped) if (appPlatform.isDesktop) { SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView(it) }, disabled = stopped, extraPadding = true) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index d03b758565..98989a775a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -33,10 +33,12 @@ import chat.simplex.res.MR @Composable fun UserAddressView( chatModel: ChatModel, + rhId: Long?, viaCreateLinkView: Boolean = false, shareViaProfile: Boolean = false, close: () -> Unit ) { + // TODO close when remote host changes val shareViaProfile = remember { mutableStateOf(shareViaProfile) } var progressIndicator by remember { mutableStateOf(false) } val onCloseHandler: MutableState<(close: () -> Unit) -> Unit> = remember { mutableStateOf({ _ -> }) } @@ -45,7 +47,7 @@ fun UserAddressView( progressIndicator = true withBGApi { try { - val u = chatModel.controller.apiSetProfileAddress(on) + val u = chatModel.controller.apiSetProfileAddress(rhId, on) if (u != null) { chatModel.updateUser(u) } @@ -67,7 +69,7 @@ fun UserAddressView( createAddress = { withApi { progressIndicator = true - val connReqContact = chatModel.controller.apiCreateUserAddress() + val connReqContact = chatModel.controller.apiCreateUserAddress(rhId) if (connReqContact != null) { chatModel.userAddress.value = UserContactLinkRec(connReqContact) @@ -112,7 +114,7 @@ fun UserAddressView( onConfirm = { progressIndicator = true withApi { - val u = chatModel.controller.apiDeleteUserAddress() + val u = chatModel.controller.apiDeleteUserAddress(rhId) if (u != null) { chatModel.userAddress.value = null chatModel.updateUser(u) @@ -126,7 +128,7 @@ fun UserAddressView( }, saveAas = { aas: AutoAcceptState, savedAAS: MutableState -> withBGApi { - val address = chatModel.controller.userAddressAutoAccept(aas.autoAccept) + val address = chatModel.controller.userAddressAutoAccept(rhId, aas.autoAccept) if (address != null) { chatModel.userAddress.value = address savedAAS.value = aas diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index ea4ef79d42..4bc2d92410 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -37,10 +37,10 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { close, saveProfile = { displayName, fullName, image -> withApi { - val updated = chatModel.controller.apiUpdateProfile(profile.copy(displayName = displayName.trim(), fullName = fullName, image = image)) + val updated = chatModel.controller.apiUpdateProfile(user.remoteHostId, profile.copy(displayName = displayName.trim(), fullName = fullName, image = image)) if (updated != null) { val (newProfile, _) = updated - chatModel.updateCurrentUser(newProfile) + chatModel.updateCurrentUser(user.remoteHostId, newProfile) profile = newProfile close() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index 7d3239700f..ec6d4e196e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -57,7 +57,7 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: ModalManager.end.closeModals() } withBGApi { - m.controller.changeActiveUser(user.userId, userViewPassword(user, searchTextOrPassword.value.trim())) + m.controller.changeActiveUser(user.remoteHostId, user.userId, userViewPassword(user, searchTextOrPassword.value.trim())) } }, removeUser = { user -> @@ -106,24 +106,24 @@ fun UserProfilesView(m: ChatModel, search: MutableState, profileHidden: ModalManager.start.showModalCloseable(true) { close -> ProfileActionView(UserProfileAction.UNHIDE, user) { pwd -> withBGApi { - setUserPrivacy(m) { m.controller.apiUnhideUser(user.userId, pwd) } + setUserPrivacy(m) { m.controller.apiUnhideUser(user, pwd) } close() } } } } else { - withBGApi { setUserPrivacy(m) { m.controller.apiUnhideUser(user.userId, searchTextOrPassword.value.trim()) } } + withBGApi { setUserPrivacy(m) { m.controller.apiUnhideUser(user, searchTextOrPassword.value.trim()) } } } }, muteUser = { user -> withBGApi { setUserPrivacy(m, onSuccess = { if (m.controller.appPrefs.showMuteProfileAlert.get()) showMuteProfileAlert(m.controller.appPrefs.showMuteProfileAlert) - }) { m.controller.apiMuteUser(user.userId) } + }) { m.controller.apiMuteUser(user) } } }, unmuteUser = { user -> - withBGApi { setUserPrivacy(m) { m.controller.apiUnmuteUser(user.userId) } } + withBGApi { setUserPrivacy(m) { m.controller.apiUnmuteUser(user) } } }, showHiddenProfile = { user -> ModalManager.start.showModalCloseable(true) { close -> @@ -348,14 +348,14 @@ private suspend fun doRemoveUser(m: ChatModel, user: User, users: List, de if (users.size < 2) return suspend fun deleteUser(user: User) { - m.controller.apiDeleteUser(user.userId, delSMPQueues, viewPwd) + m.controller.apiDeleteUser(user, delSMPQueues, viewPwd) m.removeUser(user) } try { if (user.activeUser) { val newActive = users.firstOrNull { u -> !u.activeUser && !u.hidden } if (newActive != null) { - m.controller.changeActiveUser_(newActive.userId, null) + m.controller.changeActiveUser_(newActive.remoteHostId, newActive.userId, null) deleteUser(user.copy(activeUser = false)) } } else { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt index cce8a3ce85..c2665109f5 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt @@ -50,22 +50,23 @@ actual fun ActiveCallView() { val call = chatModel.activeCall.value if (call != null) { Log.d(TAG, "has active call $call") + val callRh = call.remoteHostId when (val r = apiMsg.resp) { is WCallResponse.Capabilities -> withBGApi { val callType = CallType(call.localMedia, r.capabilities) - chatModel.controller.apiSendCallInvitation(call.contact, callType) + chatModel.controller.apiSendCallInvitation(callRh, call.contact, callType) chatModel.activeCall.value = call.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) } is WCallResponse.Offer -> withBGApi { - chatModel.controller.apiSendCallOffer(call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities) + chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities) chatModel.activeCall.value = call.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities) } is WCallResponse.Answer -> withBGApi { - chatModel.controller.apiSendCallAnswer(call.contact, r.answer, r.iceCandidates) + chatModel.controller.apiSendCallAnswer(callRh, call.contact, r.answer, r.iceCandidates) chatModel.activeCall.value = call.copy(callState = CallState.Negotiated) } is WCallResponse.Ice -> withBGApi { - chatModel.controller.apiSendCallExtraInfo(call.contact, r.iceCandidates) + chatModel.controller.apiSendCallExtraInfo(callRh, call.contact, r.iceCandidates) } is WCallResponse.Connection -> try { @@ -73,7 +74,7 @@ actual fun ActiveCallView() { if (callStatus == WebRTCCallStatus.Connected) { chatModel.activeCall.value = call.copy(callState = CallState.Connected, connectedAt = Clock.System.now()) } - withBGApi { chatModel.controller.apiCallStatus(call.contact, callStatus) } + withBGApi { chatModel.controller.apiCallStatus(callRh, call.contact, callStatus) } } catch (e: Error) { Log.d(TAG, "call status ${r.state.connectionState} not used") } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt index d2fa97f860..61e0e0d3ce 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt @@ -44,7 +44,7 @@ actual fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow Unit) { - PasteToConnectView(m, close) +actual fun ConnectViaLinkView(m: ChatModel, rhId: Long?, close: () -> Unit) { + // TODO this should close if remote host changes in model + PasteToConnectView(m, rhId, close) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt index f202318f16..540de40a95 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/newchat/ScanToConnectView.desktop.kt @@ -4,9 +4,10 @@ import androidx.compose.runtime.Composable import chat.simplex.common.model.ChatModel @Composable -actual fun ScanToConnectView(chatModel: ChatModel, close: () -> Unit) { +actual fun ScanToConnectView(chatModel: ChatModel, rhId: Long?, close: () -> Unit) { ConnectContactLayout( chatModel = chatModel, + rhId = rhId, incognitoPref = chatModel.controller.appPrefs.incognito, close = close ) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt index 464c286316..2d436dbbf0 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.desktop.kt @@ -4,6 +4,6 @@ import androidx.compose.runtime.Composable import chat.simplex.common.model.ServerCfg @Composable -actual fun ScanProtocolServer(onNext: (ServerCfg) -> Unit) { - ScanProtocolServerLayout(onNext) +actual fun ScanProtocolServer(rhId: Long?, onNext: (ServerCfg) -> Unit) { + ScanProtocolServerLayout(rhId, onNext) }