diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift index 64fba806a9..5959562a96 100644 --- a/apps/ios/Shared/Model/AppAPITypes.swift +++ b/apps/ios/Shared/Model/AppAPITypes.swift @@ -1053,6 +1053,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact) case networkStatus(networkStatus: NetworkStatus, connections: [String]) case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus]) + case chatInfoUpdated(user: UserRef, chatInfo: ChatInfo) case newChatItems(user: UserRef, chatItems: [AChatItem]) case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem]) case chatItemUpdated(user: UserRef, chatItem: AChatItem) @@ -1131,6 +1132,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case .contactsMerged: "contactsMerged" case .networkStatus: "networkStatus" case .networkStatuses: "networkStatuses" + case .chatInfoUpdated: "chatInfoUpdated" case .newChatItems: "newChatItems" case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated" case .chatItemUpdated: "chatItemUpdated" @@ -1204,6 +1206,7 @@ enum ChatEvent: Decodable, ChatAPIResult { case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)") case let .networkStatus(status, conns): return "networkStatus: \(String(describing: status))\nconnections: \(String(describing: conns))" case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses)) + case let .chatInfoUpdated(u, chatInfo): return withUser(u, String(describing: chatInfo)) case let .newChatItems(u, chatItems): let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n") return withUser(u, itemsString) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 9dd7fb86d9..6f1d70dc2c 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2208,6 +2208,12 @@ func processReceivedMsg(_ res: ChatEvent) async { n.networkStatuses = ns } } + case let .chatInfoUpdated(user, chatInfo): + if active(user) { + await MainActor.run { + m.updateChatInfo(chatInfo) + } + } case let .newChatItems(user, chatItems): for chatItem in chatItems { let cInfo = chatItem.chatInfo diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 4080605eef..64166929aa 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -778,7 +778,8 @@ struct ChatView: View { } case let .group(groupInfo, _): switch (groupInfo.membership.memberStatus) { - case .memAccepted: "connecting…" // TODO [short links] add member status to show transition from prepared group to started connection earlier? + case .memUnknown: groupInfo.preparedGroup?.connLinkStartedConnection == true ? "connecting…" : nil + case .memAccepted: "connecting…" default: nil } default: nil diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 5894a9c923..cc31ec1855 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -350,12 +350,13 @@ struct ComposeView: View { @UserDefault(DEFAULT_PRIVACY_SAVE_LAST_DRAFT) private var saveLastDraft = true @UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial + @AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false var body: some View { VStack(spacing: 0) { Divider() - let contact = chat.chatInfo.contact - if (contact?.nextConnectPrepared ?? false) || (chat.chatInfo.groupInfo?.nextConnectPrepared ?? false), + + if chat.chatInfo.nextConnectPrepared, let user = chatModel.currentUser { ContextProfilePickerView( chat: chat, @@ -404,6 +405,8 @@ struct ComposeView: View { default: previewView() } + let contact = chat.chatInfo.contact + if chat.chatInfo.groupInfo?.nextConnectPrepared == true { if chat.chatInfo.groupInfo?.businessChat == nil { Button(action: connectPreparedGroup) { @@ -744,7 +747,8 @@ struct ComposeView: View { await MainActor.run { hideKeyboard() } await sending() let mc = connectCheckLinkPreview() - if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) { + let incognito = chat.chatInfo.profileChangeProhibited ? chat.chatInfo.incognito : incognitoDefault + if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognito, msg: mc) { await MainActor.run { self.chatModel.updateContact(contact) NetworkModel.shared.setContactNetworkStatus(contact, .connected) @@ -761,7 +765,8 @@ struct ComposeView: View { await MainActor.run { hideKeyboard() } await sending() let mc = connectCheckLinkPreview() - if let groupInfo = await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) { + let incognito = chat.chatInfo.profileChangeProhibited ? chat.chatInfo.incognito : incognitoDefault + if let groupInfo = await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, incognito: incognito, msg: mc) { await MainActor.run { self.chatModel.updateGroup(groupInfo) clearState() diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift index 2e0b89020c..427a600627 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift @@ -38,7 +38,7 @@ struct ContextProfilePickerView: View { private func viewBody() -> some View { Group { - if !listExpanded { + if !listExpanded || chat.chatInfo.profileChangeProhibited { currentSelection() } else { profilePicker() @@ -59,7 +59,13 @@ struct ContextProfilePickerView: View { .padding(.leading, 12) .padding(.trailing) - if incognitoDefault { + if chat.chatInfo.profileChangeProhibited { + if chat.chatInfo.incognito { + incognitoOption() + } else { + profilerPickerUserOption(selectedUser) + } + } else if incognitoDefault { incognitoOption() } else { profilerPickerUserOption(selectedUser) @@ -140,15 +146,19 @@ struct ContextProfilePickerView: View { private func profilerPickerUserOption(_ user: User) -> some View { Button { - if selectedUser == user { - if !incognitoDefault { - listExpanded.toggle() - } else { - incognitoDefault = false - listExpanded = false + if !chat.chatInfo.profileChangeProhibited { + if selectedUser == user { + if !incognitoDefault { + listExpanded.toggle() + } else { + incognitoDefault = false + listExpanded = false + } + } else if selectedUser != user { + changeProfile(user) } - } else if selectedUser != user { - changeProfile(user) + } else { + showCantChangeProfileAlert() } } label: { HStack { @@ -166,7 +176,7 @@ struct ContextProfilePickerView: View { .font(.system(size: 12, weight: .bold)) .foregroundColor(theme.colors.secondary) .opacity(0.7) - } else { + } else if !chat.chatInfo.profileChangeProhibited { Image(systemName: "chevron.up") .font(.system(size: 12, weight: .bold)) .foregroundColor(theme.colors.secondary) @@ -226,11 +236,15 @@ struct ContextProfilePickerView: View { private func incognitoOption() -> some View { Button { - if incognitoDefault { - listExpanded.toggle() + if !chat.chatInfo.profileChangeProhibited { + if incognitoDefault { + listExpanded.toggle() + } else { + incognitoDefault = true + listExpanded = false + } } else { - incognitoDefault = true - listExpanded = false + showCantChangeProfileAlert() } } label : { HStack { @@ -253,7 +267,7 @@ struct ContextProfilePickerView: View { .font(.system(size: 12, weight: .bold)) .foregroundColor(theme.colors.secondary) .opacity(0.7) - } else { + } else if !chat.chatInfo.profileChangeProhibited { Image(systemName: "chevron.up") .font(.system(size: 12, weight: .bold)) .foregroundColor(theme.colors.secondary) @@ -274,6 +288,13 @@ struct ContextProfilePickerView: View { .frame(width: 38) .foregroundColor(.indigo) } + + private func showCantChangeProfileAlert() { + showAlert( + NSLocalizedString("Can't change profile", comment: "alert title"), + message: NSLocalizedString("To use another profile after connection attempt, delete the chat and use the link again.", comment: "alert message") + ) + } } #Preview { diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index d48dccc136..a36ca4472a 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1356,6 +1356,10 @@ swipe action Обаждането на члена не е позволено No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Не може да покани контакта! @@ -7688,6 +7692,10 @@ You will be prompted to complete authentication before this feature is enabled.< За поддръжка на незабавни push известия, базата данни за чат трябва да бъде мигрирана. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 82edb2559e..a8c5abae88 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1286,6 +1286,10 @@ swipe action Can't call member No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Nelze pozvat kontakt! @@ -7441,6 +7445,10 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Pro podporu doručování okamžitých upozornění musí být přenesena chat databáze. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index aef203721c..a9c28f9154 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1393,6 +1393,10 @@ swipe action Mitglied kann nicht angerufen werden No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Kontakt kann nicht eingeladen werden! @@ -8160,6 +8164,10 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Um sofortige Push-Benachrichtigungen zu unterstützen, muss die Chat-Datenbank migriert werden. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. Um die Server von **%@** zu nutzen, müssen Sie dessen Nutzungsbedingungen akzeptieren. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 51ce08b521..b7d7d24ee5 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1396,6 +1396,11 @@ swipe action Can't call member No comment provided by engineer. + + Can't change profile + Can't change profile + alert title + Can't invite contact! Can't invite contact! @@ -8186,6 +8191,11 @@ You will be prompted to complete authentication before this feature is enabled.< To support instant push notifications the chat database has to be migrated. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. To use the servers of **%@**, accept conditions of use. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index fc0d044d21..09ad3d74b1 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1393,6 +1393,10 @@ swipe action No se puede llamar al miembro No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! ¡No se puede invitar el contacto! @@ -8160,6 +8164,10 @@ Se te pedirá que completes la autenticación antes de activar esta función.Para permitir las notificaciones automáticas instantáneas, la base de datos se debe migrar. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. Para usar los servidores de **%@**, debes aceptar las condiciones de uso. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 97baa3bda4..0f2169ad05 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1265,6 +1265,10 @@ swipe action Can't call member No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Kontaktia ei voi kutsua! @@ -7413,6 +7417,10 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Keskustelujen-tietokanta on siirrettävä välittömien push-ilmoitusten tukemiseksi. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index c485f95460..235441e46c 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1388,6 +1388,10 @@ swipe action Impossible d'appeler le membre No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Impossible d'inviter le contact ! @@ -8069,6 +8073,10 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Pour prendre en charge les notifications push instantanées, la base de données du chat doit être migrée. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. Pour utiliser les serveurs de **%@**, acceptez les conditions d'utilisation. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 41488f2b98..4af99d6724 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1393,6 +1393,10 @@ swipe action Nem lehet felhívni a tagot No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Nem lehet meghívni a partnert! @@ -8160,6 +8164,10 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. A(z) **%@** kiszolgálóinak használatához fogadja el a használati feltételeket. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 01d586464b..8ed9ed2e7c 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1393,6 +1393,10 @@ swipe action Impossibile chiamare il membro No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Impossibile invitare il contatto! @@ -8160,6 +8164,10 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Per supportare le notifiche push istantanee, il database della chat deve essere migrato. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. Per usare i server di **%@**, accetta le condizioni d'uso. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 3dca9e8928..3ff281d1e6 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1315,6 +1315,10 @@ swipe action Can't call member No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! 連絡先を招待できません! @@ -7483,6 +7487,10 @@ You will be prompted to complete authentication before this feature is enabled.< インスタント プッシュ通知をサポートするには、チャット データベースを移行する必要があります。 No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index ebc0f739a0..02c6ae36df 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1393,6 +1393,10 @@ swipe action Kan lid niet bellen No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Kan contact niet uitnodigen! @@ -8160,6 +8164,10 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Om directe push meldingen te ondersteunen, moet de chat database worden gemigreerd. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. Om de servers van **%@** te gebruiken, moet u de gebruiksvoorwaarden accepteren. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 77da688b02..3c7f894acc 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1384,6 +1384,10 @@ swipe action Nie można zadzwonić do członka No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Nie można zaprosić kontaktu! @@ -7949,6 +7953,10 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Aby obsługiwać natychmiastowe powiadomienia push, należy zmigrować bazę danych czatu. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index e82d982923..c76fa2c379 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1394,6 +1394,10 @@ swipe action Не удаётся позвонить члену группы No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Нельзя пригласить контакт! @@ -8163,6 +8167,10 @@ You will be prompted to complete authentication before this feature is enabled.< Для поддержки мгновенный доставки уведомлений данные чата должны быть перемещены. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. Чтобы использовать серверы оператора **%@**, примите условия использования. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index cf7bd4cdf3..1d9c2455dc 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -1257,6 +1257,10 @@ swipe action Can't call member No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! ไม่สามารถเชิญผู้ติดต่อได้! @@ -7385,6 +7389,10 @@ You will be prompted to complete authentication before this feature is enabled.< เพื่อรองรับการแจ้งเตือนแบบทันที ฐานข้อมูลการแชทจะต้องได้รับการโยกย้าย No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index b64aefebc7..ca18f03de9 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1375,6 +1375,10 @@ swipe action Üye aranamaz No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Kişi davet edilemiyor! @@ -7974,6 +7978,10 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Anlık anlık bildirimleri desteklemek için sohbet veritabanının taşınması gerekir. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 37ce187558..d17fbbbbe2 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1393,6 +1393,10 @@ swipe action Не вдається зателефонувати користувачеві No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! Не вдається запросити контакт! @@ -8093,6 +8097,10 @@ You will be prompted to complete authentication before this feature is enabled.< Для підтримки миттєвих push-повідомлень необхідно перенести базу даних чату. No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. Щоб користуватися серверами **%@**, прийміть умови використання. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index caea28c92b..33e1811e74 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1390,6 +1390,10 @@ swipe action 无法呼叫成员 No comment provided by engineer. + + Can't change profile + alert title + Can't invite contact! 无法邀请联系人! @@ -8071,6 +8075,10 @@ You will be prompted to complete authentication before this feature is enabled.< 为了支持即时推送通知,聊天数据库必须被迁移。 No comment provided by engineer. + + To use another profile after connection attempt, delete the chat and use the link again. + alert message + To use the servers of **%@**, accept conditions of use. No comment provided by engineer. diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 4937081a42..daf7364a9b 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1346,6 +1346,26 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } + public var nextConnectPrepared: Bool { + get { + switch self { + case let .direct(contact): return contact.nextConnectPrepared + case let .group(groupInfo, _): return groupInfo.nextConnectPrepared + default: return false + } + } + } + + public var profileChangeProhibited: Bool { + get { + switch self { + case let .direct(contact): return contact.profileChangeProhibited + case let .group(groupInfo, _): return groupInfo.profileChangeProhibited + default: return false + } + } + } + public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { get { switch self { @@ -1714,6 +1734,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { public var active: Bool { get { contactStatus == .active } } public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } } public var nextConnectPrepared: Bool { preparedContact != nil && (activeConn == nil || activeConn?.connStatus == .prepared) } + public var profileChangeProhibited: Bool { activeConn != nil } public var nextAcceptContactRequest: Bool { contactRequestId != nil && (activeConn == nil || activeConn?.connStatus == .new) } public var sendMsgToConnect: Bool { nextSendGrpInv || nextConnectPrepared } public var displayName: String { localAlias == "" ? profile.displayName : localAlias } @@ -2069,6 +2090,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { public var apiId: Int64 { get { groupId } } public var ready: Bool { get { true } } public var nextConnectPrepared: Bool { if let preparedGroup { !preparedGroup.connLinkStartedConnection } else { false } } + public var profileChangeProhibited: Bool { preparedGroup?.connLinkPreparedConnection ?? false } public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias } public var fullName: String { get { groupProfile.fullName } } public var image: String? { get { groupProfile.image } } @@ -2117,6 +2139,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { public struct PreparedGroup: Decodable, Hashable { public var connLinkToConnect: CreatedConnLink + public var connLinkPreparedConnection: Bool public var connLinkStartedConnection: Bool } 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 c4328a6016..5cd9f51f75 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 @@ -1288,6 +1288,8 @@ interface SomeChat { val ready: Boolean val chatDeleted: Boolean val nextConnect: Boolean + val nextConnectPrepared: Boolean + val profileChangeProhibited: Boolean val incognito: Boolean fun featureEnabled(feature: ChatFeature): Boolean val timedMessagesTTL: Int? @@ -1368,6 +1370,8 @@ sealed class ChatInfo: SomeChat, NamedChat { override val ready get() = contact.ready override val chatDeleted get() = contact.chatDeleted override val nextConnect get() = contact.nextConnect + override val nextConnectPrepared get() = contact.nextConnectPrepared + override val profileChangeProhibited get() = contact.profileChangeProhibited override val incognito get() = contact.incognito override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature) override val timedMessagesTTL: Int? get() = contact.timedMessagesTTL @@ -1393,6 +1397,8 @@ sealed class ChatInfo: SomeChat, NamedChat { override val ready get() = groupInfo.ready override val chatDeleted get() = groupInfo.chatDeleted override val nextConnect get() = groupInfo.nextConnect + override val nextConnectPrepared get() = groupInfo.nextConnectPrepared + override val profileChangeProhibited get() = groupInfo.profileChangeProhibited override val incognito get() = groupInfo.incognito override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature) override val timedMessagesTTL: Int? get() = groupInfo.timedMessagesTTL @@ -1417,6 +1423,8 @@ sealed class ChatInfo: SomeChat, NamedChat { override val ready get() = noteFolder.ready override val chatDeleted get() = noteFolder.chatDeleted override val nextConnect get() = noteFolder.nextConnect + override val nextConnectPrepared get() = noteFolder.nextConnectPrepared + override val profileChangeProhibited get() = noteFolder.profileChangeProhibited override val incognito get() = noteFolder.incognito override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature) override val timedMessagesTTL: Int? get() = noteFolder.timedMessagesTTL @@ -1441,6 +1449,8 @@ sealed class ChatInfo: SomeChat, NamedChat { override val ready get() = contactRequest.ready override val chatDeleted get() = contactRequest.chatDeleted override val nextConnect get() = contactRequest.nextConnect + override val nextConnectPrepared get() = contactRequest.nextConnectPrepared + override val profileChangeProhibited get() = contactRequest.profileChangeProhibited override val incognito get() = contactRequest.incognito override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature) override val timedMessagesTTL: Int? get() = contactRequest.timedMessagesTTL @@ -1465,6 +1475,8 @@ sealed class ChatInfo: SomeChat, NamedChat { override val ready get() = contactConnection.ready override val chatDeleted get() = contactConnection.chatDeleted override val nextConnect get() = contactConnection.nextConnect + override val nextConnectPrepared get() = contactConnection.nextConnectPrepared + override val profileChangeProhibited get() = contactConnection.profileChangeProhibited override val incognito get() = contactConnection.incognito override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature) override val timedMessagesTTL: Int? get() = contactConnection.timedMessagesTTL @@ -1494,6 +1506,8 @@ sealed class ChatInfo: SomeChat, NamedChat { override val ready get() = false override val chatDeleted get() = false override val nextConnect get() = false + override val nextConnectPrepared get() = false + override val profileChangeProhibited get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = false override val timedMessagesTTL: Int? get() = null @@ -1688,7 +1702,8 @@ data class Contact( val active get() = contactStatus == ContactStatus.Active override val nextConnect get() = sendMsgToConnect val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent - val nextConnectPrepared get() = preparedContact != null && (activeConn == null || activeConn.connStatus == ConnStatus.Prepared) + override val nextConnectPrepared get() = preparedContact != null && (activeConn == null || activeConn.connStatus == ConnStatus.Prepared) + override val profileChangeProhibited get() = activeConn != null val nextAcceptContactRequest get() = contactRequestId != null && (activeConn == null || activeConn.connStatus == ConnStatus.New) val sendMsgToConnect get() = nextSendGrpInv || nextConnectPrepared override val incognito get() = contactConnIncognito @@ -1942,7 +1957,8 @@ data class GroupInfo ( override val apiId get() = groupId override val ready get() = membership.memberActive override val nextConnect get() = nextConnectPrepared - val nextConnectPrepared = if (preparedGroup != null) !preparedGroup.connLinkStartedConnection else false + override val nextConnectPrepared = if (preparedGroup != null) !preparedGroup.connLinkStartedConnection else false + override val profileChangeProhibited get() = preparedGroup?.connLinkPreparedConnection ?: false override val chatDeleted get() = false override val incognito get() = membership.memberIncognito override fun featureEnabled(feature: ChatFeature) = when (feature) { @@ -2015,6 +2031,7 @@ data class GroupInfo ( @Serializable data class PreparedGroup ( val connLinkToConnect: CreatedConnLink, + val connLinkPreparedConnection: Boolean, val connLinkStartedConnection: Boolean ) @@ -2396,6 +2413,8 @@ class NoteFolder( override val chatDeleted get() = false override val ready get() = true override val nextConnect get() = false + override val nextConnectPrepared get() = false + override val profileChangeProhibited get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice override val timedMessagesTTL: Int? get() = null @@ -2432,6 +2451,8 @@ class UserContactRequest ( override val chatDeleted get() = false override val ready get() = true override val nextConnect get() = false + override val nextConnectPrepared get() = false + override val profileChangeProhibited get() = false override val incognito get() = false override fun featureEnabled(feature: ChatFeature) = false override val timedMessagesTTL: Int? get() = null @@ -2473,6 +2494,8 @@ class PendingContactConnection( override val chatDeleted get() = false override val ready get() = false override val nextConnect get() = false + override val nextConnectPrepared get() = false + override val profileChangeProhibited get() = false override val incognito get() = customUserProfileId != null override fun featureEnabled(feature: ChatFeature) = false override val timedMessagesTTL: Int? get() = null 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 64d091a8af..394d598675 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 @@ -2510,6 +2510,12 @@ object ChatController { chatModel.networkStatuses[s.agentConnId] = s.networkStatus } } + is CR.ChatInfoUpdated -> + if (active(r.user)) { + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatInfo(rhId, r.chatInfo) + } + } is CR.NewChatItems -> withBGApi { r.chatItems.forEach { chatItem -> val cInfo = chatItem.chatInfo @@ -5959,6 +5965,7 @@ sealed class CR { // TODO remove above @Serializable @SerialName("networkStatus") class NetworkStatusResp(val networkStatus: NetworkStatus, val connections: List): CR() @Serializable @SerialName("networkStatuses") class NetworkStatuses(val user_: UserRef?, val networkStatuses: List): CR() + @Serializable @SerialName("chatInfoUpdated") class ChatInfoUpdated(val user: UserRef, val chatInfo: ChatInfo): CR() @Serializable @SerialName("newChatItems") class NewChatItems(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemsStatusesUpdated") class ChatItemsStatusesUpdated(val user: UserRef, val chatItems: List): CR() @Serializable @SerialName("chatItemUpdated") class ChatItemUpdated(val user: UserRef, val chatItem: AChatItem): CR() @@ -6144,6 +6151,7 @@ sealed class CR { is ContactSubSummary -> "contactSubSummary" is NetworkStatusResp -> "networkStatus" is NetworkStatuses -> "networkStatuses" + is ChatInfoUpdated -> "chatInfoUpdated" is NewChatItems -> "newChatItems" is ChatItemsStatusesUpdated -> "chatItemsStatusesUpdated" is ChatItemUpdated -> "chatItemUpdated" @@ -6321,6 +6329,7 @@ sealed class CR { is ContactSubSummary -> withUser(user, json.encodeToString(contactSubscriptions)) is NetworkStatusResp -> "networkStatus $networkStatus\nconnections: $connections" is NetworkStatuses -> withUser(user_, json.encodeToString(networkStatuses)) + is ChatInfoUpdated -> withUser(user, json.encodeToString(chatInfo)) is NewChatItems -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemsStatusesUpdated -> withUser(user, chatItems.joinToString("\n") { json.encodeToString(it) }) is ChatItemUpdated -> withUser(user, json.encodeToString(chatItem)) 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 6c7e775397..b77713831b 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 @@ -729,7 +729,8 @@ private fun connectingText(chatInfo: ChatInfo): String? { is ChatInfo.Group -> when (chatInfo.groupInfo.membership.memberStatus) { - GroupMemberStatus.MemAccepted -> generalGetString(MR.strings.group_connection_pending) // TODO [short links] add member status to show transition from prepared group to started connection earlier? + GroupMemberStatus.MemUnknown -> if (chatInfo.groupInfo.preparedGroup?.connLinkStartedConnection == true) generalGetString(MR.strings.group_connection_pending) else null + GroupMemberStatus.MemAccepted -> generalGetString(MR.strings.group_connection_pending) else -> null } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt index db02425d1f..ce023e83c9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextProfilePickerView.kt @@ -65,7 +65,7 @@ fun ComposeContextProfilePickerView( Modifier.size(20.dp), tint = MaterialTheme.colors.secondary, ) - } else { + } else if (!chat.chatInfo.profileChangeProhibited) { Icon( painterResource( MR.images.ic_chevron_up @@ -103,14 +103,21 @@ fun ComposeContextProfilePickerView( keepingChatId = chat.id ) if (chatModel.currentUser.value?.userId != newUser.userId) { - AlertManager.shared.showAlertMsg(generalGetString( - MR.strings.switching_profile_error_title), + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.switching_profile_error_title), String.format(generalGetString(MR.strings.switching_profile_error_message), newUser.chatViewName) ) } } } + fun showCantChangeProfileAlert() { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.context_user_picker_cant_change_profile_alert_title), + generalGetString(MR.strings.context_user_picker_cant_change_profile_alert_message) + ) + } + @Composable fun ProfilePickerUserOption(user: User) { Row( @@ -118,15 +125,19 @@ fun ComposeContextProfilePickerView( .fillMaxWidth() .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp) .clickable(onClick = { - if (selectedUser.value.userId == user.userId) { - if (!incognitoDefault) { - listExpanded.value = !listExpanded.value + if (!chat.chatInfo.profileChangeProhibited) { + if (selectedUser.value.userId == user.userId) { + if (!incognitoDefault) { + listExpanded.value = !listExpanded.value + } else { + chatModel.controller.appPrefs.incognito.set(false) + listExpanded.value = false + } } else { - chatModel.controller.appPrefs.incognito.set(false) - listExpanded.value = false + changeProfile(user) } } else { - changeProfile(user) + showCantChangeProfileAlert() } }) .padding(horizontal = DEFAULT_PADDING_HALF, vertical = 4.dp), @@ -156,11 +167,15 @@ fun ComposeContextProfilePickerView( .fillMaxWidth() .sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp) .clickable(onClick = { - if (incognitoDefault) { - listExpanded.value = !listExpanded.value + if (!chat.chatInfo.profileChangeProhibited) { + if (incognitoDefault) { + listExpanded.value = !listExpanded.value + } else { + chatModel.controller.appPrefs.incognito.set(true) + listExpanded.value = false + } } else { - chatModel.controller.appPrefs.incognito.set(true) - listExpanded.value = false + showCantChangeProfileAlert() } }) .padding(horizontal = DEFAULT_PADDING_HALF, vertical = 4.dp), @@ -265,7 +280,13 @@ fun ComposeContextProfilePickerView( color = MaterialTheme.colors.secondary ) - if (incognitoDefault) { + if (chat.chatInfo.profileChangeProhibited) { + if (chat.chatInfo.incognito) { + IncognitoOption() + } else { + ProfilePickerUserOption(selectedUser.value) + } + } else if (incognitoDefault) { IncognitoOption() } else { ProfilePickerUserOption(selectedUser.value) @@ -273,9 +294,9 @@ fun ComposeContextProfilePickerView( } } - if (listExpanded.value) { - ProfilePicker() - } else { + if (!listExpanded.value || chat.chatInfo.profileChangeProhibited) { CurrentSelection() + } else { + ProfilePicker() } } 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 d75069b922..5e34bfc0df 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 @@ -532,10 +532,11 @@ fun ComposeView( suspend fun sendConnectPreparedContact() { val mc = checkLinkPreview() sending() + val incognito = if (chat.chatInfo.profileChangeProhibited) chat.chatInfo.incognito else chatModel.controller.appPrefs.incognito.get() val contact = chatModel.controller.apiConnectPreparedContact( rh = chat.remoteHostId, contactId = chat.chatInfo.apiId, - incognito = chatModel.controller.appPrefs.incognito.get(), + incognito = incognito, msg = mc ) if (contact != null) { @@ -573,10 +574,11 @@ fun ComposeView( suspend fun connectPreparedGroup() { val mc = checkLinkPreview() sending() + val incognito = if (chat.chatInfo.profileChangeProhibited) chat.chatInfo.incognito else chatModel.controller.appPrefs.incognito.get() val groupInfo = chatModel.controller.apiConnectPreparedGroup( rh = chat.remoteHostId, groupId = chat.chatInfo.apiId, - incognito = chatModel.controller.appPrefs.incognito.get(), + incognito = incognito, msg = mc ) if (groupInfo != null) { @@ -1328,12 +1330,7 @@ fun ComposeView( Column { val currentUser = chatModel.currentUser.value - if (( - (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.nextConnectPrepared) - || (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.nextConnectPrepared) - ) - && currentUser != null - ) { + if (chat.chatInfo.nextConnectPrepared && currentUser != null) { ComposeContextProfilePickerView( rhId = rhId, chat = chat, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index b60c55c3b9..8f312d579e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -870,6 +870,8 @@ Your profile + Can\'t change profile + To use another profile after connection attempt, delete the chat and use the link again. Scan code diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 51117f9b72..8cb54c3af6 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -109,6 +109,7 @@ library Simplex.Chat.Store.Postgres.Migrations.M20250513_group_scope Simplex.Chat.Store.Postgres.Migrations.M20250526_short_links Simplex.Chat.Store.Postgres.Migrations.M20250702_contact_requests_remove_cascade_delete + Simplex.Chat.Store.Postgres.Migrations.M20250704_groups_conn_link_prepared_connection else exposed-modules: Simplex.Chat.Archive @@ -243,6 +244,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20250513_group_scope Simplex.Chat.Store.SQLite.Migrations.M20250526_short_links Simplex.Chat.Store.SQLite.Migrations.M20250702_contact_requests_remove_cascade_delete + Simplex.Chat.Store.SQLite.Migrations.M20250704_groups_conn_link_prepared_connection other-modules: Paths_simplex_chat hs-source-dirs: diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index b1b9543723..0061b38022 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -768,6 +768,7 @@ data ChatEvent | CEvtGroupMemberSwitch {user :: User, groupInfo :: GroupInfo, member :: GroupMember, switchProgress :: SwitchProgress} | CEvtContactRatchetSync {user :: User, contact :: Contact, ratchetSyncProgress :: RatchetSyncProgress} | CEvtGroupMemberRatchetSync {user :: User, groupInfo :: GroupInfo, member :: GroupMember, ratchetSyncProgress :: RatchetSyncProgress} + | CEvtChatInfoUpdated {user :: User, chatInfo :: AChatInfo} | CEvtNewChatItems {user :: User, chatItems :: [AChatItem]} -- there is the same command response | CEvtChatItemsStatusesUpdated {user :: User, chatItems :: [AChatItem]} | CEvtChatItemUpdated {user :: User, chatItem :: AChatItem} -- there is the same command response diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 26abb4b813..40bf3f0d13 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1171,7 +1171,7 @@ processChatCommand' vr = \case pure $ CRAcceptingContactRequest user ct where acceptCReq user cReq contactUsed = do - (ct, conn@Connection {connId}, sqSecured) <- acceptContactRequest user cReq incognito + (ct, conn, sqSecured) <- acceptContactRequest user cReq incognito ct' <- withStore' $ \db -> do updateContactAccepted db user ct contactUsed conn' <- @@ -1820,7 +1820,12 @@ processChatCommand' vr = \case case preparedContact of Nothing -> throwCmdError "contact doesn't have link to connect" Just PreparedContact {connLinkToConnect = ACCL SCMInvitation ccLink} -> do - (_, customUserProfile) <- connectViaInvitation user incognito ccLink (Just contactId) + (_, customUserProfile) <- connectViaInvitation user incognito ccLink (Just contactId) `catchChatError` \e -> do + -- get updated contact, in case connection was started - in UI it would lock ability to change + -- user or incognito profile for contact, in case server received request while client got network error + ct' <- withFastStore $ \db -> getContact db vr user contactId + toView $ CEvtChatInfoUpdated user (AChatInfo SCTDirect $ DirectChat ct') + throwError e -- get updated contact with connection ct' <- withFastStore $ \db -> getContact db vr user contactId forM_ msgContent_ $ \mc -> do @@ -1836,7 +1841,13 @@ processChatCommand' vr = \case smId <- getSharedMsgId withFastStore' $ \db -> setRequestSharedMsgIdForContact db contactId smId pure (smId, mc) - connectViaContact user (Just $ PCEContact ct) incognito ccLink welcomeSharedMsgId msg_ >>= \case + r <- connectViaContact user (Just $ PCEContact ct) incognito ccLink welcomeSharedMsgId msg_ `catchChatError` \e -> do + -- get updated contact, in case connection was started - in UI it would lock ability to change + -- user or incognito profile for contact, in case server received request while client got network error + ct' <- withFastStore $ \db -> getContact db vr user contactId + toView $ CEvtChatInfoUpdated user (AChatInfo SCTDirect $ DirectChat ct') + throwError e + case r of CVRSentInvitation _conn customUserProfile -> do -- get updated contact with connection ct' <- withFastStore $ \db -> getContact db vr user contactId @@ -1856,7 +1867,13 @@ processChatCommand' vr = \case smId <- getSharedMsgId withFastStore' $ \db -> setRequestSharedMsgIdForGroup db groupId smId pure (smId, mc) - connectViaContact user (Just $ PCEGroup gInfo hostMember) incognito connLinkToConnect welcomeSharedMsgId msg_ >>= \case + r <- connectViaContact user (Just $ PCEGroup gInfo hostMember) incognito connLinkToConnect welcomeSharedMsgId msg_ `catchChatError` \e -> do + -- get updated group info, in case connection was started (connLinkPreparedConnection) - in UI it would lock ability to change + -- user or incognito profile for group or business chat, in case server received request while client got network error + gInfo' <- withFastStore $ \db -> getGroupInfo db vr user groupId + toView $ CEvtChatInfoUpdated user (AChatInfo SCTGroup $ GroupChat gInfo' Nothing) + throwError e + case r of CVRSentInvitation _conn customUserProfile -> do -- get updated group info (connLinkStartedConnection and incognito membership) gInfo' <- withFastStore $ \db -> do @@ -2914,32 +2931,30 @@ processChatCommand' vr = \case connectViaInvitation user@User {userId} incognito (CCLink cReq@(CRInvitationUri crData e2e) sLnk_) contactId_ = withInvitationLock "connect" (strEncode cReq) $ do subMode <- chatReadVar subscriptionMode - -- [incognito] generate profile to send - -- TODO [short links] if incognito profile was prepared on the previous attempt, it should be used instead of creating a new one - -- TODO [short links] for connection via prepared contacts we need to: - -- - potentially use different flow or pass contact as parameter here, - -- - prohibit changing user/incognito on the second attempt in the UI - incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing - let profileToSend = userProfileToSend user incognitoProfile Nothing False lift (withAgent' $ \a -> connRequestPQSupport a PQSupportOn cReq) >>= \case Nothing -> throwChatError CEInvalidConnReq -- TODO PQ the error above should be CEIncompatibleConnReqVersion, also the same API should be called in Plan Just (agentV, pqSup') -> do let chatV = agentToChatVersion agentV - dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqs) >>= \case - Nothing -> joinNewConn chatV dm - Just (RcvDirectMsgConnection conn@Connection {connStatus, contactConnInitiated} _ct_) - | connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV dm -- own connection link - | connStatus == ConnPrepared -> joinPreparedConn conn dm -- retrying join after error + Nothing -> joinNewConn chatV + Just (RcvDirectMsgConnection conn@Connection {connStatus, contactConnInitiated, customUserProfileId} _ct_) + | connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV -- own connection link + | connStatus == ConnPrepared -> do -- retrying join after error + localIncognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId + joinPreparedConn conn (fromLocalProfile <$> localIncognitoProfile) chatV Just ent -> throwCmdError $ "connection is not RcvDirectMsgConnection: " <> show (connEntityInfo ent) where - joinNewConn chatV dm = do + joinNewConn chatV = do + -- [incognito] generate profile to send + incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup' let ccLink = CCLink cReq $ serverShortLink <$> sLnk_ - conn <- withFastStore' $ \db -> createDirectConnection' db userId connId ccLink contactId_ ConnPrepared (incognitoProfile $> profileToSend) subMode chatV pqSup' - joinPreparedConn conn dm - joinPreparedConn conn@Connection {connId = dbConnId} dm = do + conn <- withFastStore' $ \db -> createDirectConnection' db userId connId ccLink contactId_ ConnPrepared incognitoProfile subMode chatV pqSup' + joinPreparedConn conn incognitoProfile chatV + joinPreparedConn conn incognitoProfile chatV = do + let profileToSend = userProfileToSend user incognitoProfile Nothing False + dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend (sqSecured, _serviceId) <- withAgent $ \a -> joinConnection a (aUserId user) (aConnId conn) True cReq dm pqSup' subMode let newStatus = if sqSecured then ConnSndReady else ConnJoined conn' <- withFastStore' $ \db -> updateConnectionStatusFromTo db conn ConnPrepared newStatus @@ -2984,28 +2999,13 @@ processChatCommand' vr = \case Just Connection {xContactId} -> connect' groupLinkId cReqHash1 xContactId Nothing -> connect' groupLinkId cReqHash1 Nothing where - joinPreparedConn' xContactId_ conn@Connection {customUserProfileId = pId_} inGroup = do + joinPreparedConn' xContactId_ conn@Connection {customUserProfileId} inGroup = do + when (incognito /= isJust customUserProfileId) $ throwCmdError "incognito mode is different from prepared connection" xContactId <- mkXContactId xContactId_ - incognitoProfile <- getOrCreateIncognitoProfile + localIncognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId + let incognitoProfile = fromLocalProfile <$> localIncognitoProfile conn' <- joinContact user conn cReq incognitoProfile xContactId welcomeSharedMsgId msg_ inGroup PQSupportOn pure $ CVRSentInvitation conn' incognitoProfile - where - getOrCreateIncognitoProfile - | incognito = - withStore' $ \db -> case pId_ of - Nothing -> newIncognitoProfile db - Just pId -> - runExceptT (getProfileById db userId pId) - >>= either (\_ -> newIncognitoProfile db) (pure . Just . fromLocalProfile) - | otherwise = do - when (isJust pId_) $ withStore' $ \db -> - deleteIncognitoConnectionProfile db userId conn - pure Nothing - newIncognitoProfile db = do - p <- generateRandomProfile - createdAt <- liftIO getCurrentTime - void $ createIncognitoProfile_ db userId createdAt p - pure $ Just p mkXContactId = maybe (XContactId <$> drgRandomBytes 16) pure connect' groupLinkId cReqHash xContactId_ = do let inGroup = isJust groupLinkId @@ -3045,7 +3045,7 @@ processChatCommand' vr = \case connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup pure (connId, chatV) joinContact :: User -> Connection -> ConnReqContact -> Maybe Profile -> XContactId -> Maybe SharedMsgId -> Maybe (SharedMsgId, MsgContent) -> Bool -> PQSupport -> CM Connection - joinContact user conn@Connection {connId = dbConnId, connChatVersion = chatV} cReq incognitoProfile xContactId welcomeSharedMsgId msg_ inGroup pqSup = do + joinContact user conn@Connection {connChatVersion = chatV} cReq incognitoProfile xContactId welcomeSharedMsgId msg_ inGroup pqSup = do let profileToSend = userProfileToSend user incognitoProfile Nothing inGroup dm <- encodeConnInfoPQ pqSup chatV (XContact profileToSend (Just xContactId) welcomeSharedMsgId msg_) subMode <- chatReadVar subscriptionMode diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 4055e583df..ff27020aaa 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -909,7 +909,7 @@ acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId = Just conn@Connection {customUserProfileId} -> do incognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId pure (ct, conn, ExistingIncognito <$> incognitoProfile) - let profileToSend = profileToSendOnAccept user incognitoProfile False + let profileToSend = userProfileToSend' user incognitoProfile (Just ct) False dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend -- TODO [certs rcv] (ct,conn,) . fst <$> withAgent (\a -> acceptContact a (aUserId user) (aConnId conn) True invId dm pqSup' subMode) @@ -922,7 +922,7 @@ acceptContactRequestAsync UserContactRequest {agentInvitationId = AgentInvId cReqInvId, cReqChatVRange, xContactId, pqSupport = cReqPQSup} incognitoProfile = do subMode <- chatReadVar subscriptionMode - let profileToSend = profileToSendOnAccept user incognitoProfile False + let profileToSend = userProfileToSend' user incognitoProfile (Just ct) False vr <- chatVersionRange let chatV = vr `peerConnChatVersion` cReqChatVRange (cmdId, acId) <- agentAcceptContactAsync user True cReqInvId (XInfo profileToSend) subMode cReqPQSup chatV @@ -951,7 +951,7 @@ acceptGroupJoinRequestAsync (groupMemberId, memberId) <- withStore $ \db -> createJoiningMember db gVar user gInfo cReqChatVRange cReqProfile cReqXContactId_ welcomeMsgId_ gLinkMemRole initialStatus currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo - let Profile {displayName} = profileToSendOnAccept user incognitoProfile True + let Profile {displayName} = userProfileToSend' user incognitoProfile Nothing True GroupMember {memberRole = userRole, memberId = userMemberId} = membership msg = XGrpLinkInv $ @@ -1010,7 +1010,7 @@ acceptBusinessJoinRequestAsync clientMember@GroupMember {groupMemberId, memberId} UserContactRequest {agentInvitationId = AgentInvId cReqInvId, cReqChatVRange, xContactId} = do vr <- chatVersionRange - let userProfile@Profile {displayName, preferences} = profileToSendOnAccept user Nothing True + let userProfile@Profile {displayName, preferences} = userProfileToSend' user Nothing Nothing True -- TODO [short links] take groupPreferences from group info groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences msg = diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 4f82823118..31bf0b743f 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -849,8 +849,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when (groupFeatureAllowed SGFHistory gInfo'' && not memberIsCustomer) $ sendHistory user gInfo'' m' where sendXGrpLinkMem gInfo'' = do - let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo'' - profileToSend = profileToSendOnAccept user profileMode True + let incognitoProfile = ExistingIncognito <$> incognitoMembershipProfile gInfo'' + profileToSend = userProfileToSend' user incognitoProfile Nothing True void $ sendDirectMemberMessage conn (XGrpLinkMem profileToSend) groupId _ -> do unless (memberPending m) $ withStore' $ \db -> updateGroupMemberStatus db userId m GSMemConnected diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 4180b7d03c..3334653a2c 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -140,7 +140,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupInfo {membership} diff --git a/src/Simplex/Chat/Store/ContactRequest.hs b/src/Simplex/Chat/Store/ContactRequest.hs index b919db090d..ee68de5af2 100644 --- a/src/Simplex/Chat/Store/ContactRequest.hs +++ b/src/Simplex/Chat/Store/ContactRequest.hs @@ -202,7 +202,7 @@ createOrUpdateContactRequest ct <- getContact db vr user contactId pure $ RSCurrentRequest Nothing ucr (Just $ REContact ct) createBusinessChat = do - let Profile {preferences = userPreferences} = profileToSendOnAccept user Nothing True + let Profile {preferences = userPreferences} = userProfileToSend' user Nothing Nothing True groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs userPreferences (gInfo@GroupInfo {groupId}, clientMember) <- createBusinessRequestGroup db vr gVar user cReqChatVRange profile profileId ldn groupPreferences diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index cf1ec045d3..f142da4553 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -176,7 +176,7 @@ createConnReqConnection db userId acId preparedEntity_ cReqHash sLnk xContactId ) connId <- insertedRowId db case preparedEntity_ of - Just (PCEGroup gInfo _) -> updatePreparedGroup gInfo connId customUserProfileId currentTs + Just (PCEGroup gInfo _) -> updatePreparedGroup gInfo customUserProfileId currentTs _ -> pure () pure Connection @@ -214,8 +214,11 @@ createConnReqConnection db userId acId preparedEntity_ cReqHash sLnk xContactId Just (PCEContact Contact {contactId}) -> (ConnContact, Just contactId, Nothing, Just contactId) Just (PCEGroup _ GroupMember {groupMemberId}) -> (ConnMember, Nothing, Just groupMemberId, Just groupMemberId) Nothing -> (ConnContact, Nothing, Nothing, Nothing) - updatePreparedGroup GroupInfo {groupId, membership} pccConnId customUserProfileId currentTs = do - setViaGroupLinkHash db groupId pccConnId + updatePreparedGroup GroupInfo {groupId, membership} customUserProfileId currentTs = do + DB.execute + db + "UPDATE groups SET via_group_link_uri_hash = ?, conn_link_prepared_connection = ?, updated_at = ? WHERE group_id = ?" + (cReqHash, BI True, currentTs, groupId) when (isJust customUserProfileId) $ DB.execute db diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index d198f1c00e..51cbcd6b61 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -939,7 +939,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, @@ -1821,7 +1821,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupInfo {membership} diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index 4b6a1071ff..02e2378d2c 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -10,6 +10,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20250512_member_admission import Simplex.Chat.Store.Postgres.Migrations.M20250513_group_scope import Simplex.Chat.Store.Postgres.Migrations.M20250526_short_links import Simplex.Chat.Store.Postgres.Migrations.M20250702_contact_requests_remove_cascade_delete +import Simplex.Chat.Store.Postgres.Migrations.M20250704_groups_conn_link_prepared_connection import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] @@ -19,7 +20,8 @@ schemaMigrations = ("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission), ("20250513_group_scope", m20250513_group_scope, Just down_m20250513_group_scope), ("20250526_short_links", m20250526_short_links, Just down_m20250526_short_links), - ("20250702_contact_requests_remove_cascade_delete", m20250702_contact_requests_remove_cascade_delete, Just down_m20250702_contact_requests_remove_cascade_delete) + ("20250702_contact_requests_remove_cascade_delete", m20250702_contact_requests_remove_cascade_delete, Just down_m20250702_contact_requests_remove_cascade_delete), + ("20250704_groups_conn_link_prepared_connection", m20250704_groups_conn_link_prepared_connection, Just down_m20250704_groups_conn_link_prepared_connection) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20250704_groups_conn_link_prepared_connection.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20250704_groups_conn_link_prepared_connection.hs new file mode 100644 index 0000000000..dae6cd60ad --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20250704_groups_conn_link_prepared_connection.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20250704_groups_conn_link_prepared_connection where + +import Data.Text (Text) +import qualified Data.Text as T +import Text.RawString.QQ (r) + +m20250704_groups_conn_link_prepared_connection :: Text +m20250704_groups_conn_link_prepared_connection = + T.pack + [r| +ALTER TABLE groups ADD COLUMN conn_link_prepared_connection SMALLINT NOT NULL DEFAULT 0; +|] + +down_m20250704_groups_conn_link_prepared_connection :: Text +down_m20250704_groups_conn_link_prepared_connection = + T.pack + [r| +ALTER TABLE groups DROP COLUMN conn_link_prepared_connection; +|] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index 40e4979c68..4115c8a491 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -645,7 +645,8 @@ CREATE TABLE test_chat_schema.groups ( conn_short_link_to_connect bytea, conn_link_started_connection smallint DEFAULT 0 NOT NULL, welcome_shared_msg_id bytea, - request_shared_msg_id bytea + request_shared_msg_id bytea, + conn_link_prepared_connection smallint DEFAULT 0 NOT NULL ); diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index 02d8e4776e..ed78c8092a 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -133,6 +133,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250512_member_admission import Simplex.Chat.Store.SQLite.Migrations.M20250513_group_scope import Simplex.Chat.Store.SQLite.Migrations.M20250526_short_links import Simplex.Chat.Store.SQLite.Migrations.M20250702_contact_requests_remove_cascade_delete +import Simplex.Chat.Store.SQLite.Migrations.M20250704_groups_conn_link_prepared_connection import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -265,7 +266,8 @@ schemaMigrations = ("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission), ("20250513_group_scope", m20250513_group_scope, Just down_m20250513_group_scope), ("20250526_short_links", m20250526_short_links, Just down_m20250526_short_links), - ("20250702_contact_requests_remove_cascade_delete", m20250702_contact_requests_remove_cascade_delete, Just down_m20250702_contact_requests_remove_cascade_delete) + ("20250702_contact_requests_remove_cascade_delete", m20250702_contact_requests_remove_cascade_delete, Just down_m20250702_contact_requests_remove_cascade_delete), + ("20250704_groups_conn_link_prepared_connection", m20250704_groups_conn_link_prepared_connection, Just down_m20250704_groups_conn_link_prepared_connection) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20250704_groups_conn_link_prepared_connection.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250704_groups_conn_link_prepared_connection.hs new file mode 100644 index 0000000000..e8f559969a --- /dev/null +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20250704_groups_conn_link_prepared_connection.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.SQLite.Migrations.M20250704_groups_conn_link_prepared_connection where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20250704_groups_conn_link_prepared_connection :: Query +m20250704_groups_conn_link_prepared_connection = + [sql| +ALTER TABLE groups ADD COLUMN conn_link_prepared_connection INTEGER NOT NULL DEFAULT 0; +|] + +down_m20250704_groups_conn_link_prepared_connection :: Query +down_m20250704_groups_conn_link_prepared_connection = + [sql| +ALTER TABLE groups DROP COLUMN conn_link_prepared_connection; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 8815eea9ec..2ff3164c5d 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -63,7 +63,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupInfo {membership} @@ -966,7 +966,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupInfo {membership} @@ -1016,7 +1016,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, @@ -4682,7 +4682,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupMember - membership @@ -4708,7 +4708,7 @@ Query: g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupMember - membership @@ -6228,6 +6228,10 @@ Query: UPDATE groups SET user_member_profile_sent_at = ? WHERE user_id = ? AND g Plan: SEARCH groups USING INTEGER PRIMARY KEY (rowid=?) +Query: UPDATE groups SET via_group_link_uri_hash = ?, conn_link_prepared_connection = ?, updated_at = ? WHERE group_id = ? +Plan: +SEARCH groups USING INTEGER PRIMARY KEY (rowid=?) + Query: UPDATE note_folders SET chat_ts = ? WHERE user_id = ? AND note_folder_id = ? Plan: SEARCH note_folders USING INTEGER PRIMARY KEY (rowid=?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index b41bc560ab..913a99b6ec 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -145,7 +145,8 @@ CREATE TABLE groups( conn_short_link_to_connect BLOB, conn_link_started_connection INTEGER NOT NULL DEFAULT 0, welcome_shared_msg_id BLOB, - request_shared_msg_id BLOB, -- received + request_shared_msg_id BLOB, + conn_link_prepared_connection INTEGER NOT NULL DEFAULT 0, -- received FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE CASCADE diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index eb754e4bf4..90246f0710 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -434,21 +434,6 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId = |] (userId, profileId, userId, profileId, userId, profileId) -deleteIncognitoConnectionProfile :: DB.Connection -> UserId -> Connection -> IO () -deleteIncognitoConnectionProfile db userId Connection {connId, customUserProfileId} = - forM_ customUserProfileId $ \profileId -> do - DB.execute db "UPDATE connections SET custom_user_profile_id = NULL WHERE connection_id = ?" (Only connId) - DB.execute - db - [sql| - DELETE FROM contact_profiles - WHERE user_id = ? AND contact_profile_id = ? - AND NOT EXISTS (SELECT 1 FROM contacts WHERE contact_profile_id = ?) - AND NOT EXISTS (SELECT 1 FROM contact_requests WHERE contact_profile_id = ?) - AND NOT EXISTS (SELECT 1 FROM group_members WHERE contact_profile_id = ? OR member_profile_id = ?) - |] - (userId, profileId, profileId, profileId, profileId, profileId) - type PreparedContactRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink, Maybe SharedMsgId, Maybe SharedMsgId) type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) @@ -623,7 +608,7 @@ safeDeleteLDN db User {userId} localDisplayName = do |] (userId, localDisplayName, userId) -type PreparedGroupRow = (Maybe ConnReqContact, Maybe ShortLinkContact, BoolInt, Maybe SharedMsgId, Maybe SharedMsgId) +type PreparedGroupRow = (Maybe ConnReqContact, Maybe ShortLinkContact, BoolInt, BoolInt, Maybe SharedMsgId, Maybe SharedMsgId) type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId) @@ -643,8 +628,8 @@ toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, toPreparedGroup :: PreparedGroupRow -> Maybe PreparedGroup toPreparedGroup = \case - (Just fullLink, shortLink_, BI connLinkStartedConnection, welcomeSharedMsgId, requestSharedMsgId) -> - Just PreparedGroup {connLinkToConnect = CCLink fullLink shortLink_, connLinkStartedConnection, welcomeSharedMsgId, requestSharedMsgId} + (Just fullLink, shortLink_, BI connLinkPreparedConnection, BI connLinkStartedConnection, welcomeSharedMsgId, requestSharedMsgId) -> + Just PreparedGroup {connLinkToConnect = CCLink fullLink shortLink_, connLinkPreparedConnection, connLinkStartedConnection, welcomeSharedMsgId, requestSharedMsgId} _ -> Nothing toGroupMember :: Int64 -> GroupMemberRow -> GroupMember @@ -679,7 +664,7 @@ groupInfoQuery = g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, - g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, + g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, -- GroupMember - membership diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 6da47f5058..739f5943b9 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -494,6 +494,7 @@ instance ToField BusinessChatType where toField = toField . textEncode data PreparedGroup = PreparedGroup { connLinkToConnect :: CreatedLinkContact, + connLinkPreparedConnection :: Bool, connLinkStartedConnection :: Bool, welcomeSharedMsgId :: Maybe SharedMsgId, -- it is stored only for business chats, and only if welcome message is specified requestSharedMsgId :: Maybe SharedMsgId @@ -646,10 +647,10 @@ redactedMemberProfile Profile {displayName, fullName, image} = data IncognitoProfile = NewIncognito Profile | ExistingIncognito LocalProfile -profileToSendOnAccept :: User -> Maybe IncognitoProfile -> Bool -> Profile -profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> ip) Nothing +userProfileToSend' :: User -> Maybe IncognitoProfile -> Maybe Contact -> Bool -> Profile +userProfileToSend' user ip = userProfileToSend user (fromIncognitoProfile <$> ip) where - getIncognitoProfile = \case + fromIncognitoProfile = \case NewIncognito p -> p ExistingIncognito lp -> fromLocalProfile lp diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 5a09a7b2ce..71b6a0aef1 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -386,6 +386,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView} CEvtGroupMemberSwitch u g m progress -> ttyUser u $ viewGroupMemberSwitch g m progress CEvtContactRatchetSync u ct progress -> ttyUser u $ viewContactRatchetSync ct progress CEvtGroupMemberRatchetSync u g m progress -> ttyUser u $ viewGroupMemberRatchetSync g m progress + CEvtChatInfoUpdated u chatInfo -> [] CEvtNewChatItems u chatItems -> viewChatItems ttyUser unmuted u chatItems ts tz CEvtChatItemsStatusesUpdated u chatItems | length chatItems <= 20 ->