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 ->