diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
index c17d8e23a8..29c87ff7e0 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
@@ -577,7 +577,7 @@ struct ChatInfoView: View {
private func clearChatAlert() -> Alert {
Alert(
title: Text("Clear conversation?"),
- message: Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
+ message: Text(chat.chatInfo.displayName + "\n\n") + Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
primaryButton: .destructive(Text("Clear")) {
Task {
await clearChat(chat)
@@ -1185,6 +1185,7 @@ private func deleteContactOrConversationDialog(
showActionSheet(SomeActionSheet(
actionSheet: ActionSheet(
title: Text("Delete contact?"),
+ message: Text(contact.displayName),
buttons: [
.destructive(Text("Only delete conversation")) {
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .messages, dismissToChatList, showAlert)
@@ -1331,6 +1332,7 @@ private func deleteContactWithoutConversation(
showActionSheet(SomeActionSheet(
actionSheet: ActionSheet(
title: Text("Confirm contact deletion?"),
+ message: Text(contact.displayName),
buttons: [
.destructive(Text("Delete and notify contact")) {
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .full(notify: true), dismissToChatList, showAlert)
@@ -1355,6 +1357,7 @@ private func deleteNotReadyContact(
showActionSheet(SomeActionSheet(
actionSheet: ActionSheet(
title: Text("Confirm contact deletion?"),
+ message: Text(contact.displayName),
buttons: [
.destructive(Text("Confirm")) {
deleteContactMaybeErrorAlert(chat, contact, chatDeleteMode: .full(notify: false), dismissToChatList, showAlert)
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
index 0a448a2772..1353f590fa 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
@@ -845,7 +845,7 @@ struct GroupChatInfoView: View {
let label: LocalizedStringKey = groupInfo.useRelays ? "Delete channel?" : groupInfo.businessChat == nil ? "Delete group?" : "Delete chat?"
return Alert(
title: Text(label),
- message: deleteGroupAlertMessage(groupInfo),
+ message: Text(chat.chatInfo.displayName + "\n\n") + deleteGroupAlertMessage(groupInfo),
primaryButton: .destructive(Text("Delete")) {
Task {
do {
@@ -867,7 +867,7 @@ struct GroupChatInfoView: View {
private func clearChatAlert() -> Alert {
Alert(
title: Text("Clear conversation?"),
- message: Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
+ message: Text(chat.chatInfo.displayName + "\n\n") + Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
primaryButton: .destructive(Text("Clear")) {
Task {
await clearChat(chat)
@@ -889,7 +889,7 @@ struct GroupChatInfoView: View {
)
return Alert(
title: Text(titleLabel),
- message: Text(messageLabel),
+ message: Text(chat.chatInfo.displayName + "\n\n") + Text(messageLabel),
primaryButton: .destructive(Text("Leave")) {
Task {
await leaveGroup(chat.chatInfo.apiId)
diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
index b4590fc124..76734dcb42 100644
--- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
@@ -568,7 +568,7 @@ struct ChatListNavLink: View {
let label: LocalizedStringKey = groupInfo.useRelays ? "Delete channel?" : groupInfo.businessChat == nil ? "Delete group?" : "Delete chat?"
return Alert(
title: Text(label),
- message: deleteGroupAlertMessage(groupInfo),
+ message: Text(chat.chatInfo.displayName + "\n\n") + deleteGroupAlertMessage(groupInfo),
primaryButton: .destructive(Text("Delete")) {
Task { await deleteChat(chat) }
},
@@ -600,7 +600,7 @@ struct ChatListNavLink: View {
private func clearChatAlert() -> Alert {
Alert(
title: Text("Clear conversation?"),
- message: Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
+ message: Text(chat.chatInfo.displayName + "\n\n") + Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
primaryButton: .destructive(Text("Clear")) {
Task { await clearChat(chat) }
},
@@ -630,7 +630,7 @@ struct ChatListNavLink: View {
)
return Alert(
title: Text(titleLabel),
- message: Text(messageLabel),
+ message: Text(chat.chatInfo.displayName + "\n\n") + Text(messageLabel),
primaryButton: .destructive(Text("Leave")) {
Task { await leaveGroup(groupInfo.groupId) }
},
@@ -701,10 +701,10 @@ func rejectContactRequestAlert(_ contactRequestId: Int64) -> Alert {
func deleteContactConnectionAlert(_ contactConnection: PendingContactConnection, showError: @escaping (ErrorAlert) -> Void, success: @escaping () -> Void = {}) -> Alert {
Alert(
title: Text("Delete pending connection?"),
- message:
- contactConnection.initiated
- ? Text("The contact you shared this link with will NOT be able to connect!")
- : Text("The connection you accepted will be cancelled!"),
+ message: Text(contactConnection.displayName + "\n\n")
+ + (contactConnection.initiated
+ ? Text("The contact you shared this link with will NOT be able to connect!")
+ : Text("The connection you accepted will be cancelled!")),
primaryButton: .destructive(Text("Delete")) {
Task {
do {
diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift
index d5d70abaea..278893a669 100644
--- a/apps/ios/Shared/Views/Database/DatabaseView.swift
+++ b/apps/ios/Shared/Views/Database/DatabaseView.swift
@@ -110,33 +110,88 @@ struct DatabaseView: View {
}
Section {
- settingsRow(
- stopped ? "exclamationmark.octagon.fill" : "play.fill",
- color: stopped ? .red : .green
- ) {
- Toggle(
- stopped ? "Chat is stopped" : "Chat is running",
- isOn: $runChat
- )
- .onChange(of: runChat) { _ in
- if runChat {
- DatabaseView.startChat($runChat, $progressIndicator)
- } else if !stoppingChat {
- stoppingChat = false
- alert = .stopChat
- }
- }
- }
- } header: {
- Text("Run chat")
- .foregroundColor(theme.colors.secondary)
- } footer: {
- if case .documents = dbContainer {
- Text("Database will be migrated when the app restarts")
- .foregroundColor(theme.colors.secondary)
- }
+ NavigationLink("Database passphrase & export", destination: databaseManagementView)
}
+ Section {
+ Button(m.users.count > 1 ? "Delete files for all chat profiles" : "Delete all files", role: .destructive) {
+ alert = .deleteFilesAndMedia
+ }
+ .disabled(progressIndicator || appFilesCountAndSize?.0 == 0)
+ } header: {
+ Text("Files & media")
+ .foregroundColor(theme.colors.secondary)
+ } footer: {
+ if let (fileCount, size) = appFilesCountAndSize {
+ if fileCount == 0 {
+ Text("No received or sent files")
+ .foregroundColor(theme.colors.secondary)
+ } else {
+ Text("\(fileCount) file(s) with total size of \(ByteCountFormatter.string(fromByteCount: Int64(size), countStyle: .binary))")
+ .foregroundColor(theme.colors.secondary)
+ }
+ }
+ }
+ }
+ .onAppear {
+ runChat = m.chatRunning ?? true
+ appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
+ currentChatItemTTL = chatItemTTL
+ }
+ .onChange(of: chatItemTTL) { ttl in
+ if ttl < currentChatItemTTL {
+ alert = .setChatItemTTL(ttl: ttl)
+ } else if ttl != currentChatItemTTL {
+ setCiTTL(ttl)
+ }
+ }
+ .alert(item: $alert) { item in databaseAlert(item) }
+ .fileImporter(
+ isPresented: $showFileImporter,
+ allowedContentTypes: [.zip],
+ allowsMultipleSelection: false
+ ) { result in
+ if case let .success(files) = result, let fileURL = files.first {
+ importedArchivePath = fileURL
+ alert = .importArchive
+ }
+ }
+ }
+
+ private func runChatToggleView() -> some View {
+ Section {
+ let stopped = m.chatRunning == false
+ settingsRow(
+ stopped ? "exclamationmark.octagon.fill" : "play.fill",
+ color: stopped ? .red : .green
+ ) {
+ Toggle(
+ stopped ? "Chat is stopped" : "Chat is running",
+ isOn: $runChat
+ )
+ .onChange(of: runChat) { _ in
+ if runChat {
+ DatabaseView.startChat($runChat, $progressIndicator)
+ } else if !stoppingChat {
+ stoppingChat = false
+ alert = .stopChat
+ }
+ }
+ }
+ } header: {
+ Text("Run chat")
+ .foregroundColor(theme.colors.secondary)
+ } footer: {
+ if case .documents = dbContainer {
+ Text("Database will be migrated when the app restarts")
+ .foregroundColor(theme.colors.secondary)
+ }
+ }
+ }
+
+ private func databaseManagementView() -> some View {
+ List {
+ let stopped = m.chatRunning == false
Section {
let unencrypted = m.chatDbEncrypted == false
let color: Color = unencrypted ? .orange : theme.colors.secondary
@@ -194,49 +249,9 @@ struct DatabaseView: View {
}
}
- Section {
- Button(m.users.count > 1 ? "Delete files for all chat profiles" : "Delete all files", role: .destructive) {
- alert = .deleteFilesAndMedia
- }
- .disabled(progressIndicator || appFilesCountAndSize?.0 == 0)
- } header: {
- Text("Files & media")
- .foregroundColor(theme.colors.secondary)
- } footer: {
- if let (fileCount, size) = appFilesCountAndSize {
- if fileCount == 0 {
- Text("No received or sent files")
- .foregroundColor(theme.colors.secondary)
- } else {
- Text("\(fileCount) file(s) with total size of \(ByteCountFormatter.string(fromByteCount: Int64(size), countStyle: .binary))")
- .foregroundColor(theme.colors.secondary)
- }
- }
- }
- }
- .onAppear {
- runChat = m.chatRunning ?? true
- appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
- currentChatItemTTL = chatItemTTL
- }
- .onChange(of: chatItemTTL) { ttl in
- if ttl < currentChatItemTTL {
- alert = .setChatItemTTL(ttl: ttl)
- } else if ttl != currentChatItemTTL {
- setCiTTL(ttl)
- }
- }
- .alert(item: $alert) { item in databaseAlert(item) }
- .fileImporter(
- isPresented: $showFileImporter,
- allowedContentTypes: [.zip],
- allowsMultipleSelection: false
- ) { result in
- if case let .success(files) = result, let fileURL = files.first {
- importedArchivePath = fileURL
- alert = .importArchive
- }
+ runChatToggleView()
}
+ .modifier(ThemedBackground(grouped: true))
}
private func databaseAlert(_ alertItem: DatabaseAlert) -> Alert {
diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift
index f10b945dc0..24cf088918 100644
--- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift
+++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift
@@ -121,16 +121,6 @@ struct NetworkAndServers: View {
}
}
- Section(header: Text("Calls").foregroundColor(theme.colors.secondary)) {
- NavigationLink {
- RTCServers()
- .navigationTitle("Your ICE servers")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- Text("WebRTC ICE servers")
- }
- }
-
Section(header: Text("Network connection").foregroundColor(theme.colors.secondary)) {
HStack {
Text(m.networkInfo.networkType.text)
diff --git a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift
index c4d0588987..131eeecef7 100644
--- a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift
+++ b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift
@@ -63,36 +63,6 @@ struct NotificationsView: View {
}
}
- NavigationLink {
- List {
- Section {
- SelectionListView(list: NotificationPreviewMode.values, selection: $m.notificationPreview) { previewMode in
- ntfPreviewModeGroupDefault.set(previewMode)
- m.notificationPreview = previewMode
- }
- } footer: {
- VStack(alignment: .leading, spacing: 1) {
- Text("You can set lock screen notification preview via settings.")
- .foregroundColor(theme.colors.secondary)
- Button("Open Settings") {
- DispatchQueue.main.async {
- UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
- }
- }
- }
- }
- }
- .navigationTitle("Show preview")
- .modifier(ThemedBackground(grouped: true))
- .navigationBarTitleDisplayMode(.inline)
- } label: {
- HStack {
- Text("Show preview")
- Spacer()
- Text(m.notificationPreview.label)
- }
- }
-
if let server = m.notificationServer {
smpServers("Push server", [server], theme.colors.secondary)
testTokenButton(server)
diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
index 3ae9f0eacd..ad6b2d4454 100644
--- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift
@@ -81,30 +81,12 @@ struct PrivacySettings: View {
settingsRow("link", color: theme.colors.secondary) {
Toggle("Remove link tracking", isOn: $privacySanitizeLinks)
}
- settingsRow("message", color: theme.colors.secondary) {
- Toggle("Show last messages", isOn: $showChatPreviews)
- }
- settingsRow("rectangle.and.pencil.and.ellipsis", color: theme.colors.secondary) {
- Toggle("Message draft", isOn: $saveLastDraft)
- }
- .onChange(of: saveLastDraft) { saveDraft in
- if !saveDraft {
- m.draft = nil
- m.draftChatId = nil
- }
- }
} header: {
Text("Chats")
.foregroundColor(theme.colors.secondary)
}
Section {
- settingsRow("lock.doc", color: theme.colors.secondary) {
- Toggle("Encrypt local files", isOn: $encryptLocalFiles)
- .onChange(of: encryptLocalFiles) {
- setEncryptLocalFiles($0)
- }
- }
settingsRow("photo", color: theme.colors.secondary) {
Toggle("Auto-accept images", isOn: $autoAcceptImages)
.onChange(of: autoAcceptImages) {
@@ -126,20 +108,9 @@ struct PrivacySettings: View {
}
}
}
- settingsRow("network.badge.shield.half.filled", color: theme.colors.secondary) {
- Toggle("Protect IP address", isOn: $askToApproveRelays)
- }
} header: {
Text("Files")
.foregroundColor(theme.colors.secondary)
- } footer: {
- if askToApproveRelays {
- Text("The app will ask to confirm downloads from unknown file servers (except .onion).")
- .foregroundColor(theme.colors.secondary)
- } else {
- Text("Without Tor or VPN, your IP address will be visible to file servers.")
- .foregroundColor(theme.colors.secondary)
- }
}
Section {
@@ -155,45 +126,8 @@ struct PrivacySettings: View {
}
Section {
- settingsRow("person", color: theme.colors.secondary) {
- Toggle("Contacts", isOn: $contactReceipts)
- }
- settingsRow("person.2", color: theme.colors.secondary) {
- Toggle("Small groups (max 20)", isOn: $groupReceipts)
- }
- } header: {
- Text("Send delivery receipts to")
- .foregroundColor(theme.colors.secondary)
- } footer: {
- VStack(alignment: .leading) {
- Text("These settings are for your current profile **\(m.currentUser?.displayName ?? "")**.")
- Text("They can be overridden in contact and group settings.")
- }
- .foregroundColor(theme.colors.secondary)
- .frame(maxWidth: .infinity, alignment: .leading)
- }
- .confirmationDialog(contactReceiptsDialogTitle, isPresented: $contactReceiptsDialogue, titleVisibility: .visible) {
- Button(contactReceipts ? "Enable (keep overrides)" : "Disable (keep overrides)") {
- setSendReceiptsContacts(contactReceipts, clearOverrides: false)
- }
- Button(contactReceipts ? "Enable for all" : "Disable for all", role: .destructive) {
- setSendReceiptsContacts(contactReceipts, clearOverrides: true)
- }
- Button("Cancel", role: .cancel) {
- contactReceiptsReset = true
- contactReceipts.toggle()
- }
- }
- .confirmationDialog(groupReceiptsDialogTitle, isPresented: $groupReceiptsDialogue, titleVisibility: .visible) {
- Button(groupReceipts ? "Enable (keep overrides)" : "Disable (keep overrides)") {
- setSendReceiptsGroups(groupReceipts, clearOverrides: false)
- }
- Button(groupReceipts ? "Enable for all" : "Disable for all", role: .destructive) {
- setSendReceiptsGroups(groupReceipts, clearOverrides: true)
- }
- Button("Cancel", role: .cancel) {
- groupReceiptsReset = true
- groupReceipts.toggle()
+ NavigationLink(destination: morePrivacyView) {
+ settingsRow("ellipsis", color: theme.colors.secondary) { Text("More privacy") }
}
}
}
@@ -243,6 +177,132 @@ struct PrivacySettings: View {
}
}
+ @ViewBuilder
+ private func morePrivacyView() -> some View {
+ List {
+ Section {
+ settingsRow("message", color: theme.colors.secondary) {
+ Toggle("Show last messages", isOn: $showChatPreviews)
+ }
+ settingsRow("rectangle.and.pencil.and.ellipsis", color: theme.colors.secondary) {
+ Toggle("Message draft", isOn: $saveLastDraft)
+ }
+ .onChange(of: saveLastDraft) { saveDraft in
+ if !saveDraft {
+ m.draft = nil
+ m.draftChatId = nil
+ }
+ }
+ } header: {
+ Text("Chats")
+ .foregroundColor(theme.colors.secondary)
+ }
+
+ Section {
+ settingsRow("lock.doc", color: theme.colors.secondary) {
+ Toggle("Encrypt local files", isOn: $encryptLocalFiles)
+ .onChange(of: encryptLocalFiles) {
+ setEncryptLocalFiles($0)
+ }
+ }
+ settingsRow("network.badge.shield.half.filled", color: theme.colors.secondary) {
+ Toggle("Protect IP address", isOn: $askToApproveRelays)
+ }
+ } header: {
+ Text("Files")
+ .foregroundColor(theme.colors.secondary)
+ } footer: {
+ if askToApproveRelays {
+ Text("The app will ask to confirm downloads from unknown file servers (except .onion).")
+ .foregroundColor(theme.colors.secondary)
+ } else {
+ Text("Without Tor or VPN, your IP address will be visible to file servers.")
+ .foregroundColor(theme.colors.secondary)
+ }
+ }
+
+ Section {
+ NavigationLink {
+ List {
+ Section {
+ SelectionListView(list: NotificationPreviewMode.values, selection: $m.notificationPreview) { previewMode in
+ ntfPreviewModeGroupDefault.set(previewMode)
+ m.notificationPreview = previewMode
+ }
+ } footer: {
+ VStack(alignment: .leading, spacing: 1) {
+ Text("You can set lock screen notification preview via settings.")
+ .foregroundColor(theme.colors.secondary)
+ Button("Open Settings") {
+ DispatchQueue.main.async {
+ UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
+ }
+ }
+ }
+ }
+ }
+ .navigationTitle("Show preview")
+ .modifier(ThemedBackground(grouped: true))
+ .navigationBarTitleDisplayMode(.inline)
+ } label: {
+ HStack {
+ Text("Show preview")
+ Spacer()
+ Text(m.notificationPreview.label)
+ }
+ }
+ } header: {
+ Text("Notifications")
+ .foregroundColor(theme.colors.secondary)
+ }
+
+ Section {
+ settingsRow("person", color: theme.colors.secondary) {
+ Toggle("Contacts", isOn: $contactReceipts)
+ }
+ settingsRow("person.2", color: theme.colors.secondary) {
+ Toggle("Small groups (max 20)", isOn: $groupReceipts)
+ }
+ } header: {
+ Text("Send delivery receipts to")
+ .foregroundColor(theme.colors.secondary)
+ } footer: {
+ VStack(alignment: .leading) {
+ Text("These settings are for your current profile **\(m.currentUser?.displayName ?? "")**.")
+ Text("They can be overridden in contact and group settings.")
+ }
+ .foregroundColor(theme.colors.secondary)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .confirmationDialog(contactReceiptsDialogTitle, isPresented: $contactReceiptsDialogue, titleVisibility: .visible) {
+ Button(contactReceipts ? "Enable (keep overrides)" : "Disable (keep overrides)") {
+ setSendReceiptsContacts(contactReceipts, clearOverrides: false)
+ }
+ Button(contactReceipts ? "Enable for all" : "Disable for all", role: .destructive) {
+ setSendReceiptsContacts(contactReceipts, clearOverrides: true)
+ }
+ Button("Cancel", role: .cancel) {
+ contactReceiptsReset = true
+ contactReceipts.toggle()
+ }
+ }
+ .confirmationDialog(groupReceiptsDialogTitle, isPresented: $groupReceiptsDialogue, titleVisibility: .visible) {
+ Button(groupReceipts ? "Enable (keep overrides)" : "Disable (keep overrides)") {
+ setSendReceiptsGroups(groupReceipts, clearOverrides: false)
+ }
+ Button(groupReceipts ? "Enable for all" : "Disable for all", role: .destructive) {
+ setSendReceiptsGroups(groupReceipts, clearOverrides: true)
+ }
+ Button("Cancel", role: .cancel) {
+ groupReceiptsReset = true
+ groupReceipts.toggle()
+ }
+ }
+ }
+ .navigationTitle("More privacy")
+ .modifier(ThemedBackground(grouped: true))
+ }
+
private func setEncryptLocalFiles(_ enable: Bool) {
do {
try apiSetEncryptLocalFiles(enable)
diff --git a/apps/ios/Shared/Views/UserSettings/SetDeliveryReceiptsView.swift b/apps/ios/Shared/Views/UserSettings/SetDeliveryReceiptsView.swift
index e03dace43d..e46edbc5af 100644
--- a/apps/ios/Shared/Views/UserSettings/SetDeliveryReceiptsView.swift
+++ b/apps/ios/Shared/Views/UserSettings/SetDeliveryReceiptsView.swift
@@ -69,7 +69,7 @@ struct SetDeliveryReceiptsView: View {
Button {
AlertManager.shared.showAlert(Alert(
title: Text("Delivery receipts are disabled!"),
- message: Text("You can enable them later via app Privacy & Security settings."),
+ message: Text("You can enable them later via app Your privacy settings."),
primaryButton: .default(Text("Don't show again")) {
m.setDeliveryReceipts = false
privacyDeliveryReceiptsSet.set(true)
diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
index a903329454..93f32a53a6 100644
--- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift
+++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
@@ -290,47 +290,7 @@ struct SettingsView: View {
func settingsView() -> some View {
List {
- let user = chatModel.currentUser
- Section(header: Text("Settings").foregroundColor(theme.colors.secondary)) {
- NavigationLink {
- NotificationsView()
- .navigationTitle("Notifications")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- HStack {
- notificationsIcon()
- Text("Notifications")
- }
- }
- .disabled(chatModel.chatRunning != true)
-
- NavigationLink {
- NetworkAndServers()
- .navigationTitle("Network & servers")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") }
- }
- .disabled(chatModel.chatRunning != true)
-
- NavigationLink {
- CallSettings()
- .navigationTitle("Your calls")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") }
- }
- .disabled(chatModel.chatRunning != true)
-
- NavigationLink {
- PrivacySettings()
- .navigationTitle("Your privacy")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- settingsRow("lock", color: theme.colors.secondary) { Text("Privacy & security") }
- }
- .disabled(chatModel.chatRunning != true)
-
+ Section(header: Text(verbatim: "").foregroundColor(theme.colors.secondary)) {
if UIApplication.shared.supportsAlternateIcons {
NavigationLink {
AppearanceSettings()
@@ -341,10 +301,24 @@ struct SettingsView: View {
}
.disabled(chatModel.chatRunning != true)
}
- }
- Section(header: Text("Chat database").foregroundColor(theme.colors.secondary)) {
+ NavigationLink {
+ PrivacySettings()
+ .navigationTitle("Your privacy")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ settingsRow("lock", color: theme.colors.secondary) { Text("Your privacy") }
+ }
+ .disabled(chatModel.chatRunning != true)
+
+ NavigationLink {
+ helpAndSupportView
+ } label: {
+ settingsRow("questionmark", color: theme.colors.secondary) { Text("Help & support") }
+ }
+
chatDatabaseRow()
+
NavigationLink {
MigrateFromDevice(showProgressOnSettings: $showProgress)
.toolbar {
@@ -360,6 +334,58 @@ struct SettingsView: View {
}
}
+ Section(header: Text("Advanced settings").foregroundColor(theme.colors.secondary)) {
+ NavigationLink {
+ NetworkAndServers()
+ .navigationTitle("Network & servers")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ settingsRow("externaldrive.connected.to.line.below", color: theme.colors.secondary) { Text("Network & servers") }
+ }
+ .disabled(chatModel.chatRunning != true)
+
+ NavigationLink {
+ NotificationsView()
+ .navigationTitle("Notifications")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ HStack {
+ notificationsIcon()
+ Text("Notifications")
+ }
+ }
+ .disabled(chatModel.chatRunning != true)
+
+ NavigationLink {
+ CallSettings()
+ .navigationTitle("Your calls")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ settingsRow("video", color: theme.colors.secondary) { Text("Audio & video calls") }
+ }
+ .disabled(chatModel.chatRunning != true)
+
+ NavigationLink {
+ VersionView()
+ .navigationBarTitle("App version")
+ .modifier(ThemedBackground())
+ } label: {
+ Text(verbatim: "v\(appVersion ?? "?")")
+ }
+ }
+ }
+ .navigationTitle("Your settings")
+ .modifier(ThemedBackground(grouped: true))
+ .onDisappear {
+ chatModel.showingTerminal = false
+ chatModel.terminalItems = []
+ }
+ }
+
+ @ViewBuilder
+ private var helpAndSupportView: some View {
+ List {
+ let user = chatModel.currentUser
Section(header: Text("Help").foregroundColor(theme.colors.secondary)) {
if let user = user {
NavigationLink {
@@ -378,6 +404,7 @@ struct SettingsView: View {
} label: {
settingsRow("plus", color: theme.colors.secondary) { Text("What's new") }
}
+
NavigationLink {
SimpleXInfo(onboarding: false)
.navigationBarTitle("", displayMode: .inline)
@@ -386,6 +413,9 @@ struct SettingsView: View {
} label: {
settingsRow("info", color: theme.colors.secondary) { Text("About SimpleX Chat") }
}
+ }
+
+ Section(header: Text("Contact").foregroundColor(theme.colors.secondary)) {
settingsRow("number", color: theme.colors.secondary) {
Button("Send questions and ideas") {
dismiss()
@@ -398,7 +428,7 @@ struct SettingsView: View {
settingsRow("envelope", color: theme.colors.secondary) { Text("[Send us email](mailto:chat@simplex.chat)") }
}
- Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) {
+ Section(header: Text("Support the project").foregroundColor(theme.colors.secondary)) {
settingsRow("keyboard", color: theme.colors.secondary) {
ExternalLink("Contribute", destination: URL(string: "https://github.com/simplex-chat/simplex-chat#contribute")!)
}
@@ -421,42 +451,21 @@ struct SettingsView: View {
}
}
}
-
- Section(header: Text("Develop").foregroundColor(theme.colors.secondary)) {
- NavigationLink {
- DeveloperView()
- .navigationTitle("Developer tools")
- .modifier(ThemedBackground(grouped: true))
- } label: {
- settingsRow("chevron.left.forwardslash.chevron.right", color: theme.colors.secondary) { Text("Developer tools") }
- }
- NavigationLink {
- VersionView()
- .navigationBarTitle("App version")
- .modifier(ThemedBackground())
- } label: {
- Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))")
- }
- }
}
- .navigationTitle("Your settings")
+ .navigationTitle("Help & support")
.modifier(ThemedBackground(grouped: true))
- .onDisappear {
- chatModel.showingTerminal = false
- chatModel.terminalItems = []
- }
}
-
+
private func chatDatabaseRow() -> some View {
NavigationLink {
DatabaseView(dismissSettingsSheet: dismiss, chatItemTTL: chatModel.chatItemTTL)
- .navigationTitle("Your chat database")
+ .navigationTitle("Chat data")
.modifier(ThemedBackground(grouped: true))
} label: {
let color: Color = chatModel.chatDbEncrypted == false ? .orange : theme.colors.secondary
settingsRow("internaldrive", color: color) {
HStack {
- Text("Database passphrase & export")
+ Text("Chat data")
Spacer()
if chatModel.chatRunning == false {
Image(systemName: "exclamationmark.octagon.fill").foregroundColor(.red)
diff --git a/apps/ios/Shared/Views/UserSettings/VersionView.swift b/apps/ios/Shared/Views/UserSettings/VersionView.swift
index 0fc2b4cb3e..e30c11699e 100644
--- a/apps/ios/Shared/Views/UserSettings/VersionView.swift
+++ b/apps/ios/Shared/Views/UserSettings/VersionView.swift
@@ -10,21 +10,33 @@ import SwiftUI
import SimpleXChat
struct VersionView: View {
+ @EnvironmentObject var theme: AppTheme
@State var versionInfo: CoreVersionInfo?
var body: some View {
- VStack(alignment: .leading) {
- Text("App version: v\(appVersion ?? "?")")
- Text("App build: \(appBuild ?? "?")")
- if let info = versionInfo {
- Text("Core version: v\(info.version)")
- if let v = try? AttributedString(markdown: "simplexmq: v\(info.simplexmqVersion) ([\(info.simplexmqCommit.prefix(7))](https://github.com/simplex-chat/simplexmq/commit/\(info.simplexmqCommit)))") {
- Text(v)
+ List {
+ Section {
+ Text("App version: v\(appVersion ?? "?")")
+ Text("App build: \(appBuild ?? "?")")
+ if let info = versionInfo {
+ Text("Core version: v\(info.version)")
+ if let v = try? AttributedString(markdown: "simplexmq: v\(info.simplexmqVersion) ([\(info.simplexmqCommit.prefix(7))](https://github.com/simplex-chat/simplexmq/commit/\(info.simplexmqCommit)))") {
+ Text(v)
+ }
+ }
+ }
+
+ Section {
+ NavigationLink {
+ DeveloperView()
+ .navigationTitle("Developer")
+ .modifier(ThemedBackground(grouped: true))
+ } label: {
+ Text("Developer")
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
- .padding()
.onAppear {
do {
versionInfo = try apiGetVersion()
diff --git a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
index 427430b833..bd8d6a17ef 100644
--- a/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
+++ b/apps/ios/SimpleX Localizations/ar.xcloc/Localized Contents/ar.xliff
@@ -1157,8 +1157,8 @@
يطور
No comment provided by engineer.
-
- Developer tools
+
+ Developer
أدوات المطور
No comment provided by engineer.
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 364cee97e5..89d5014c95 100644
--- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
+++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
@@ -3014,8 +3014,8 @@ alert button
Developer options
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Инструменти за разработчици
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff
index fbda1abd29..bdb0c0ce24 100644
--- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff
+++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff
@@ -1223,8 +1223,8 @@
Develop
No comment provided by engineer.
-
- Developer tools
+
+ Developer
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 5ba29ec846..f7beecf4ba 100644
--- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
+++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
@@ -2904,8 +2904,8 @@ alert button
Developer options
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Nástroje pro vývojáře
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 797a489c92..822c13649e 100644
--- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
+++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
@@ -3130,8 +3130,8 @@ alert button
Optionen für Entwickler
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Entwicklertools
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff
index 7a560bb41b..181f51eb99 100644
--- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff
+++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff
@@ -1100,8 +1100,8 @@ Available in v5.1
Develop
No comment provided by engineer.
-
- Developer tools
+
+ Developer
No comment provided by engineer.
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 c108dcc904..bd498d53e3 100644
--- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
+++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
@@ -3140,9 +3140,9 @@ alert button
Developer options
No comment provided by engineer.
-
- Developer tools
- Developer tools
+
+ Developer
+ Developer
No comment provided by engineer.
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 d93e692a63..60de758c9c 100644
--- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
+++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
@@ -3130,8 +3130,8 @@ alert button
Opciones desarrollador
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Herramientas desarrollo
No comment provided by engineer.
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 5656516b7d..c818fdc472 100644
--- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
+++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
@@ -2791,8 +2791,8 @@ alert button
Developer options
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Kehittäjätyökalut
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 3ea0859d76..4d1616df20 100644
--- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
+++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
@@ -3039,8 +3039,8 @@ alert button
Options pour les développeurs
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Outils du développeur
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff
index f94d6cefd8..a22d30dd73 100644
--- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff
+++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff
@@ -1356,8 +1356,8 @@ Available in v5.1
לְפַתֵחַ
No comment provided by engineer.
-
- Developer tools
+
+ Developer
כלי מפתחים
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff
index 2aa945f603..33fe9a0e9c 100644
--- a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff
+++ b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff
@@ -1012,8 +1012,8 @@
Develop
No comment provided by engineer.
-
- Developer tools
+
+ Developer
No comment provided by engineer.
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 129436ecb0..11b447dc2b 100644
--- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
+++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
@@ -3130,8 +3130,8 @@ alert button
Fejlesztői beállítások
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Fejlesztői eszközök
No comment provided by engineer.
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 469da88ce2..876cdb10bb 100644
--- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
+++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
@@ -3130,8 +3130,8 @@ alert button
Opzioni sviluppatore
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Strumenti di sviluppo
No comment provided by engineer.
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 13396b13a4..ff0d085c08 100644
--- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
+++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
@@ -2891,8 +2891,8 @@ alert button
開発者向けの設定
No comment provided by engineer.
-
- Developer tools
+
+ Developer
開発ツール
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff
index ca51a875c7..01b5e2b9a2 100644
--- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff
+++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff
@@ -1141,8 +1141,8 @@
Develop
No comment provided by engineer.
-
- Developer tools
+
+ Developer
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff
index 4b51d66a34..5aa2a98854 100644
--- a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff
+++ b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff
@@ -1005,8 +1005,8 @@
Develop
No comment provided by engineer.
-
- Developer tools
+
+ Developer
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 9f1818fba9..a7670a4475 100644
--- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
+++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
@@ -3040,8 +3040,8 @@ alert button
Ontwikkelaars opties
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Ontwikkel gereedschap
No comment provided by engineer.
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 2644708927..a60b5891d4 100644
--- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
+++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
@@ -3063,8 +3063,8 @@ alert button
Opcje deweloperskie
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Narzędzia deweloperskie
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff
index d9af0624bf..032a33ff62 100644
--- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff
+++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff
@@ -1179,8 +1179,8 @@
Desenvolver
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Ferramentas de desenvolvimento
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff
index e4fac55bcb..3905130e84 100644
--- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff
+++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff
@@ -1203,8 +1203,8 @@ Available in v5.1
Develop
No comment provided by engineer.
-
- Developer tools
+
+ Developer
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 a3971c0325..22df6680f6 100644
--- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
+++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
@@ -3130,8 +3130,8 @@ alert button
Опции разработчика
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Инструменты разработчика
No comment provided by engineer.
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 cd2e30977d..11d6b28091 100644
--- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
+++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
@@ -2779,8 +2779,8 @@ alert button
Developer options
No comment provided by engineer.
-
- Developer tools
+
+ Developer
เครื่องมือสำหรับนักพัฒนา
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 1189b53e3c..b673b609b9 100644
--- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
+++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
@@ -3066,8 +3066,8 @@ alert button
Geliştirici seçenekleri
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Geliştirici araçları
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 49f9e21eda..3c0c6ef8de 100644
--- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
+++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
@@ -3050,8 +3050,8 @@ alert button
Можливості для розробників
No comment provided by engineer.
-
- Developer tools
+
+ Developer
Інструменти для розробників
No comment provided by engineer.
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 8823f17204..9001375615 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
@@ -3060,8 +3060,8 @@ alert button
开发者选项
No comment provided by engineer.
-
- Developer tools
+
+ Developer
开发者工具
No comment provided by engineer.
diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff
index 0e4e383b52..ebd6f4da71 100644
--- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff
+++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff
@@ -1148,8 +1148,8 @@
開發
No comment provided by engineer.
-
- Developer tools
+
+ Developer
開發者工具
No comment provided by engineer.
diff --git a/apps/ios/SimpleX SE/ShareModel.swift b/apps/ios/SimpleX SE/ShareModel.swift
index 18f3e2c344..9790e5944f 100644
--- a/apps/ios/SimpleX SE/ShareModel.swift
+++ b/apps/ios/SimpleX SE/ShareModel.swift
@@ -75,7 +75,7 @@ class ShareModel: ObservableObject {
func setup(context: NSExtensionContext) {
if appLocalAuthEnabledGroupDefault.get() && !allowShareExtensionGroupDefault.get() {
- errorAlert = ErrorAlert(title: "App is locked!", message: "You can allow sharing in Privacy & Security / SimpleX Lock settings.")
+ errorAlert = ErrorAlert(title: "App is locked!", message: "You can allow sharing in Your privacy / SimpleX Lock settings.")
return
}
if let item = context.inputItems.first as? NSExtensionItem,
diff --git a/apps/ios/SimpleX SE/de.lproj/Localizable.strings b/apps/ios/SimpleX SE/de.lproj/Localizable.strings
index df368686e8..1d2e5a4b01 100644
--- a/apps/ios/SimpleX SE/de.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/de.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Falsches Datenbank-Passwort";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Sie können das Teilen in den Einstellungen zu Datenschutz & Sicherheit / SimpleX-Sperre erlauben.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "Sie können das Teilen in den Einstellungen zu Datenschutz & Sicherheit / SimpleX-Sperre erlauben.";
diff --git a/apps/ios/SimpleX SE/es.lproj/Localizable.strings b/apps/ios/SimpleX SE/es.lproj/Localizable.strings
index 4cc5029537..5bb42b4f0a 100644
--- a/apps/ios/SimpleX SE/es.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/es.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Contraseña incorrecta de la base de datos";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Puedes dar permiso para compartir en Privacidad y Seguridad / Bloque SimpleX.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "Puedes dar permiso para compartir en Privacidad y Seguridad / Bloque SimpleX.";
diff --git a/apps/ios/SimpleX SE/fr.lproj/Localizable.strings b/apps/ios/SimpleX SE/fr.lproj/Localizable.strings
index 46a458b471..6b492ee882 100644
--- a/apps/ios/SimpleX SE/fr.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/fr.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Mauvaise phrase secrète pour la base de données";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Vous pouvez autoriser le partage dans les paramètres Confidentialité et sécurité / SimpleX Lock.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "Vous pouvez autoriser le partage dans les paramètres Confidentialité et sécurité / SimpleX Lock.";
diff --git a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings
index 3aad39c5d1..dae06b1d73 100644
--- a/apps/ios/SimpleX SE/hu.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/hu.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Érvénytelen adatbázis-jelmondat";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "A megosztást az Adatvédelem és biztonság / SimpleX-zár menüben engedélyezheti.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "A megosztást az Adatvédelem és biztonság / SimpleX-zár menüben engedélyezheti.";
diff --git a/apps/ios/SimpleX SE/it.lproj/Localizable.strings b/apps/ios/SimpleX SE/it.lproj/Localizable.strings
index e3d34650a3..3b27092f24 100644
--- a/apps/ios/SimpleX SE/it.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/it.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Password del database sbagliata";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Puoi consentire la condivisione in Privacy e sicurezza / impostazioni di SimpleX Lock.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "Puoi consentire la condivisione in Privacy e sicurezza / impostazioni di SimpleX Lock.";
diff --git a/apps/ios/SimpleX SE/nl.lproj/Localizable.strings b/apps/ios/SimpleX SE/nl.lproj/Localizable.strings
index e5d2487b54..050cb1a735 100644
--- a/apps/ios/SimpleX SE/nl.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/nl.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Verkeerde database wachtwoord";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "U kunt delen toestaan in de instellingen voor Privacy en beveiliging / SimpleX Lock.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "U kunt delen toestaan in de instellingen voor Privacy en beveiliging / SimpleX Lock.";
diff --git a/apps/ios/SimpleX SE/pl.lproj/Localizable.strings b/apps/ios/SimpleX SE/pl.lproj/Localizable.strings
index c563431c28..0755e1e374 100644
--- a/apps/ios/SimpleX SE/pl.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/pl.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Nieprawidłowe hasło bazy danych";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Możesz zezwolić na udostępnianie w ustawieniach Prywatność i bezpieczeństwo / Blokada SimpleX.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "Możesz zezwolić na udostępnianie w ustawieniach Prywatność i bezpieczeństwo / Blokada SimpleX.";
diff --git a/apps/ios/SimpleX SE/ru.lproj/Localizable.strings b/apps/ios/SimpleX SE/ru.lproj/Localizable.strings
index e4c8c000d4..90a5841738 100644
--- a/apps/ios/SimpleX SE/ru.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/ru.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Неправильный пароль базы данных";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Вы можете разрешить функцию Поделиться в настройках Конфиденциальности / Блокировка SimpleX.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "Вы можете разрешить функцию Поделиться в настройках Конфиденциальности / Блокировка SimpleX.";
diff --git a/apps/ios/SimpleX SE/tr.lproj/Localizable.strings b/apps/ios/SimpleX SE/tr.lproj/Localizable.strings
index baef71c127..69b122832d 100644
--- a/apps/ios/SimpleX SE/tr.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/tr.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Yanlış veritabanı parolası";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Gizlilik ve Güvenlik / SimpleX Lock ayarlarından paylaşıma izin verebilirsiniz.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "Gizlilik ve Güvenlik / SimpleX Lock ayarlarından paylaşıma izin verebilirsiniz.";
diff --git a/apps/ios/SimpleX SE/uk.lproj/Localizable.strings b/apps/ios/SimpleX SE/uk.lproj/Localizable.strings
index a6da81185e..7574470d01 100644
--- a/apps/ios/SimpleX SE/uk.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/uk.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "Неправильна ключова фраза до бази даних";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "Ви можете дозволити спільний доступ у налаштуваннях Конфіденційність і безпека / SimpleX Lock.";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "Ви можете дозволити спільний доступ у налаштуваннях Конфіденційність і безпека / SimpleX Lock.";
diff --git a/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings b/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings
index 362e2edb74..fc046ab087 100644
--- a/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings
+++ b/apps/ios/SimpleX SE/zh-Hans.lproj/Localizable.strings
@@ -107,5 +107,5 @@
"Wrong database passphrase" = "数据库密码错误";
/* No comment provided by engineer. */
-"You can allow sharing in Privacy & Security / SimpleX Lock settings." = "您可以在 \"隐私与安全\"/\"SimpleX Lock \"设置中允许共享。";
+"You can allow sharing in Your privacy / SimpleX Lock settings." = "您可以在 \"隐私与安全\"/\"SimpleX Lock \"设置中允许共享。";
diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift
index f93b090517..2d36a7a53a 100644
--- a/apps/ios/SimpleXChat/ImageUtils.swift
+++ b/apps/ios/SimpleXChat/ImageUtils.swift
@@ -297,7 +297,7 @@ private func uniqueCombine(_ fileName: String, fullPath: Bool = false) -> String
let name = ns.deletingPathExtension
let ext = ns.pathExtension
let suffix = (n == 0) ? "" : "_\(n)"
- let f = "\(name)\(suffix).\(ext)"
+ let f = ext.isEmpty ? "\(name)\(suffix)" : "\(name)\(suffix).\(ext)"
return (FileManager.default.fileExists(atPath: fullPath ? f : getAppFilePath(f).path)) ? tryCombine(fileName, n + 1) : f
}
return tryCombine(fileName, 0)
diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings
index ec869e05b4..68f2cfe502 100644
--- a/apps/ios/bg.lproj/Localizable.strings
+++ b/apps/ios/bg.lproj/Localizable.strings
@@ -1659,7 +1659,7 @@ alert button */
"Develop" = "Разработване";
/* No comment provided by engineer. */
-"Developer tools" = "Инструменти за разработчици";
+"Developer" = "Инструменти за разработчици";
/* No comment provided by engineer. */
"Device" = "Устройство";
@@ -3217,7 +3217,7 @@ alert button */
"Preview" = "Визуализация";
/* No comment provided by engineer. */
-"Privacy & security" = "Поверителност и сигурност";
+"Your privacy" = "Поверителност и сигурност";
/* No comment provided by engineer. */
"Private filenames" = "Поверителни имена на файлове";
@@ -4452,7 +4452,7 @@ server test failure */
"You can enable later via Settings" = "Можете да активирате по-късно през Настройки";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Можете да ги активирате по-късно през настройките за \"Поверителност и сигурност\" на приложението.";
+"You can enable them later via app Your privacy settings." = "Можете да ги активирате по-късно през настройките за \"Поверителност и сигурност\" на приложението.";
/* No comment provided by engineer. */
"You can give another try." = "Можете да опитате още веднъж.";
diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings
index fc4b3f0fc6..0b74ef9504 100644
--- a/apps/ios/cs.lproj/Localizable.strings
+++ b/apps/ios/cs.lproj/Localizable.strings
@@ -1306,7 +1306,7 @@ alert button */
"Develop" = "Vyvinout";
/* No comment provided by engineer. */
-"Developer tools" = "Nástroje pro vývojáře";
+"Developer" = "Nástroje pro vývojáře";
/* No comment provided by engineer. */
"Device" = "Zařízení";
@@ -2578,7 +2578,7 @@ alert button */
"Preview" = "Náhled";
/* No comment provided by engineer. */
-"Privacy & security" = "Ochrana osobních údajů a zabezpečení";
+"Your privacy" = "Ochrana osobních údajů a zabezpečení";
/* No comment provided by engineer. */
"Private filenames" = "Soukromé názvy souborů";
@@ -3543,7 +3543,7 @@ server test failure */
"You can enable later via Settings" = "Můžete povolit později v Nastavení";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Můžete je povolit později v nastavení Soukromí & Bezpečnosti aplikace";
+"You can enable them later via app Your privacy settings." = "Můžete je povolit později v nastavení Soukromí & Bezpečnosti aplikace";
/* No comment provided by engineer. */
"You can hide or mute a user profile - swipe it to the right." = "Profil uživatele můžete skrýt nebo ztlumit - přejeďte prstem doprava.";
diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings
index 2c4e37791b..80246b727b 100644
--- a/apps/ios/de.lproj/Localizable.strings
+++ b/apps/ios/de.lproj/Localizable.strings
@@ -2064,7 +2064,7 @@ alert button */
"Developer options" = "Optionen für Entwickler";
/* No comment provided by engineer. */
-"Developer tools" = "Entwicklertools";
+"Developer" = "Entwicklertools";
/* No comment provided by engineer. */
"Device" = "Gerät";
@@ -4533,7 +4533,7 @@ alert button */
"Previously connected servers" = "Bisher verbundene Server";
/* No comment provided by engineer. */
-"Privacy & security" = "Datenschutz & Sicherheit";
+"Your privacy" = "Datenschutz & Sicherheit";
/* No comment provided by engineer. */
"Privacy for your customers." = "Schutz der Privatsphäre Ihrer Kunden.";
@@ -6759,7 +6759,7 @@ server test failure */
"You can enable later via Settings" = "Sie können diese später in den Einstellungen aktivieren";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Sie können diese später in den Datenschutz & Sicherheits-Einstellungen der App aktivieren.";
+"You can enable them later via app Your privacy settings." = "Sie können diese später in den Datenschutz & Sicherheits-Einstellungen der App aktivieren.";
/* No comment provided by engineer. */
"You can give another try." = "Sie können es nochmal probieren.";
diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings
index cf03ae6dbf..e0611c0afc 100644
--- a/apps/ios/es.lproj/Localizable.strings
+++ b/apps/ios/es.lproj/Localizable.strings
@@ -2064,7 +2064,7 @@ alert button */
"Developer options" = "Opciones desarrollador";
/* No comment provided by engineer. */
-"Developer tools" = "Herramientas desarrollo";
+"Developer" = "Herramientas desarrollo";
/* No comment provided by engineer. */
"Device" = "Dispositivo";
@@ -4533,7 +4533,7 @@ alert button */
"Previously connected servers" = "Servidores conectados previamente";
/* No comment provided by engineer. */
-"Privacy & security" = "Seguridad y Privacidad";
+"Your privacy" = "Seguridad y Privacidad";
/* No comment provided by engineer. */
"Privacy for your customers." = "Privacidad para tus clientes.";
@@ -6759,7 +6759,7 @@ server test failure */
"You can enable later via Settings" = "Puedes activar más tarde en Configuración";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Puedes activarlos más tarde en la configuración de Privacidad y Seguridad.";
+"You can enable them later via app Your privacy settings." = "Puedes activarlos más tarde en la configuración de Privacidad y Seguridad.";
/* No comment provided by engineer. */
"You can give another try." = "Puedes intentarlo de nuevo.";
diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings
index 3b1bd6523c..36b516374e 100644
--- a/apps/ios/fi.lproj/Localizable.strings
+++ b/apps/ios/fi.lproj/Localizable.strings
@@ -982,7 +982,7 @@ alert button */
"Develop" = "Kehitä";
/* No comment provided by engineer. */
-"Developer tools" = "Kehittäjätyökalut";
+"Developer" = "Kehittäjätyökalut";
/* No comment provided by engineer. */
"Device" = "Laite";
@@ -2232,7 +2232,7 @@ new chat action */
"Preview" = "Esikatselu";
/* No comment provided by engineer. */
-"Privacy & security" = "Yksityisyys ja turvallisuus";
+"Your privacy" = "Yksityisyys ja turvallisuus";
/* No comment provided by engineer. */
"Private filenames" = "Yksityiset tiedostonimet";
@@ -3179,7 +3179,7 @@ server test failure */
"You can enable later via Settings" = "Voit ottaa käyttöön myöhemmin asetusten kautta";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Voit ottaa ne käyttöön myöhemmin sovelluksen Yksityisyys & Turvallisuus -asetuksista.";
+"You can enable them later via app Your privacy settings." = "Voit ottaa ne käyttöön myöhemmin sovelluksen Yksityisyys & Turvallisuus -asetuksista.";
/* No comment provided by engineer. */
"You can hide or mute a user profile - swipe it to the right." = "Voit piilottaa tai mykistää käyttäjäprofiilin pyyhkäisemällä sitä oikealle.";
diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings
index 91cd6f3078..98cdb4aec8 100644
--- a/apps/ios/fr.lproj/Localizable.strings
+++ b/apps/ios/fr.lproj/Localizable.strings
@@ -1745,7 +1745,7 @@ alert button */
"Developer options" = "Options pour les développeurs";
/* No comment provided by engineer. */
-"Developer tools" = "Outils du développeur";
+"Developer" = "Outils du développeur";
/* No comment provided by engineer. */
"Device" = "Appareil";
@@ -3742,7 +3742,7 @@ alert button */
"Previously connected servers" = "Serveurs précédemment connectés";
/* No comment provided by engineer. */
-"Privacy & security" = "Vie privée et sécurité";
+"Your privacy" = "Vie privée et sécurité";
/* No comment provided by engineer. */
"Privacy for your customers." = "Respect de la vie privée de vos clients.";
@@ -5448,7 +5448,7 @@ server test failure */
"You can enable later via Settings" = "Vous pouvez l'activer ultérieurement via Paramètres";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Vous pouvez les activer ultérieurement via les paramètres de Confidentialité et Sécurité de l'application.";
+"You can enable them later via app Your privacy settings." = "Vous pouvez les activer ultérieurement via les paramètres de Confidentialité et Sécurité de l'application.";
/* No comment provided by engineer. */
"You can give another try." = "Vous pouvez faire un nouvel essai.";
diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings
index 029fb9edd5..362d9157fd 100644
--- a/apps/ios/hu.lproj/Localizable.strings
+++ b/apps/ios/hu.lproj/Localizable.strings
@@ -2064,7 +2064,7 @@ alert button */
"Developer options" = "Fejlesztői beállítások";
/* No comment provided by engineer. */
-"Developer tools" = "Fejlesztői eszközök";
+"Developer" = "Fejlesztői eszközök";
/* No comment provided by engineer. */
"Device" = "Eszköz";
@@ -4533,7 +4533,7 @@ alert button */
"Previously connected servers" = "Korábban kapcsolódott kiszolgálók";
/* No comment provided by engineer. */
-"Privacy & security" = "Adatvédelem és biztonság";
+"Your privacy" = "Adatvédelem és biztonság";
/* No comment provided by engineer. */
"Privacy for your customers." = "Saját ügyfeleinek adatvédelme.";
@@ -6759,7 +6759,7 @@ server test failure */
"You can enable later via Settings" = "Később engedélyezheti a beállításokban";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Később engedélyezheti őket az „Adatvédelem és biztonság” menüben.";
+"You can enable them later via app Your privacy settings." = "Később engedélyezheti őket az „Adatvédelem és biztonság” menüben.";
/* No comment provided by engineer. */
"You can give another try." = "Megpróbálhatja még egyszer.";
diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings
index c882eb662c..8d4fa3532b 100644
--- a/apps/ios/it.lproj/Localizable.strings
+++ b/apps/ios/it.lproj/Localizable.strings
@@ -2064,7 +2064,7 @@ alert button */
"Developer options" = "Opzioni sviluppatore";
/* No comment provided by engineer. */
-"Developer tools" = "Strumenti di sviluppo";
+"Developer" = "Strumenti di sviluppo";
/* No comment provided by engineer. */
"Device" = "Dispositivo";
@@ -4533,7 +4533,7 @@ alert button */
"Previously connected servers" = "Server precedentemente connessi";
/* No comment provided by engineer. */
-"Privacy & security" = "Privacy e sicurezza";
+"Your privacy" = "Privacy e sicurezza";
/* No comment provided by engineer. */
"Privacy for your customers." = "Privacy per i tuoi clienti.";
@@ -6759,7 +6759,7 @@ server test failure */
"You can enable later via Settings" = "Puoi attivarle più tardi nelle impostazioni";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Puoi attivarle più tardi nelle impostazioni di privacy e sicurezza dell'app.";
+"You can enable them later via app Your privacy settings." = "Puoi attivarle più tardi nelle impostazioni di privacy e sicurezza dell'app.";
/* No comment provided by engineer. */
"You can give another try." = "Puoi fare un altro tentativo.";
diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings
index 35d8732e3f..b88eb10414 100644
--- a/apps/ios/ja.lproj/Localizable.strings
+++ b/apps/ios/ja.lproj/Localizable.strings
@@ -1270,7 +1270,7 @@ alert button */
"Developer options" = "開発者向けの設定";
/* No comment provided by engineer. */
-"Developer tools" = "開発ツール";
+"Developer" = "開発ツール";
/* No comment provided by engineer. */
"Device" = "端末";
@@ -2533,7 +2533,7 @@ alert button */
"Preview" = "プレビュー";
/* No comment provided by engineer. */
-"Privacy & security" = "プライバシーとセキュリティ";
+"Your privacy" = "プライバシーとセキュリティ";
/* No comment provided by engineer. */
"Private filenames" = "プライベートなファイル名";
@@ -3459,7 +3459,7 @@ server test failure */
"You can enable later via Settings" = "あとで設定から有効にできます";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "あとでアプリのプライバシーとセキュリティの設定から有効にすることができます。";
+"You can enable them later via app Your privacy settings." = "あとでアプリのプライバシーとセキュリティの設定から有効にすることができます。";
/* No comment provided by engineer. */
"You can hide or mute a user profile - swipe it to the right." = "ユーザープロファイルを右にスワイプすると、非表示またはミュートにすることができます。";
diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings
index 407665bbec..1c776a7bbe 100644
--- a/apps/ios/nl.lproj/Localizable.strings
+++ b/apps/ios/nl.lproj/Localizable.strings
@@ -1773,7 +1773,7 @@ alert button */
"Developer options" = "Ontwikkelaars opties";
/* No comment provided by engineer. */
-"Developer tools" = "Ontwikkel gereedschap";
+"Developer" = "Ontwikkel gereedschap";
/* No comment provided by engineer. */
"Device" = "Apparaat";
@@ -3929,7 +3929,7 @@ alert button */
"Previously connected servers" = "Eerder verbonden servers";
/* No comment provided by engineer. */
-"Privacy & security" = "Privacy en beveiliging";
+"Your privacy" = "Privacy en beveiliging";
/* No comment provided by engineer. */
"Privacy for your customers." = "Privacy voor uw klanten.";
@@ -5777,7 +5777,7 @@ server test failure */
"You can enable later via Settings" = "U kunt later inschakelen via Instellingen";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "U kunt ze later inschakelen via de privacy- en beveiligingsinstellingen van de app.";
+"You can enable them later via app Your privacy settings." = "U kunt ze later inschakelen via de privacy- en beveiligingsinstellingen van de app.";
/* No comment provided by engineer. */
"You can give another try." = "Je kunt het nog een keer proberen.";
diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings
index 8905300160..9cc6eca252 100644
--- a/apps/ios/pl.lproj/Localizable.strings
+++ b/apps/ios/pl.lproj/Localizable.strings
@@ -1845,7 +1845,7 @@ alert button */
"Developer options" = "Opcje deweloperskie";
/* No comment provided by engineer. */
-"Developer tools" = "Narzędzia deweloperskie";
+"Developer" = "Narzędzia deweloperskie";
/* No comment provided by engineer. */
"Device" = "Urządzenie";
@@ -4131,7 +4131,7 @@ alert button */
"Previously connected servers" = "Wcześniej połączone serwery";
/* No comment provided by engineer. */
-"Privacy & security" = "Prywatność i bezpieczeństwo";
+"Your privacy" = "Prywatność i bezpieczeństwo";
/* No comment provided by engineer. */
"Privacy for your customers." = "Prywatność dla Twoich klientów.";
@@ -6135,7 +6135,7 @@ server test failure */
"You can enable later via Settings" = "Możesz włączyć później w Ustawieniach";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Możesz je włączyć później w ustawieniach Prywatności i Bezpieczeństwa aplikacji.";
+"You can enable them later via app Your privacy settings." = "Możesz je włączyć później w ustawieniach Prywatności i Bezpieczeństwa aplikacji.";
/* No comment provided by engineer. */
"You can give another try." = "Możesz spróbować ponownie.";
diff --git a/apps/ios/product/README.md b/apps/ios/product/README.md
index 107c0e6569..fd25b09d01 100644
--- a/apps/ios/product/README.md
+++ b/apps/ios/product/README.md
@@ -101,7 +101,7 @@ End-to-end encrypted audio and video communication.
| Call history | Call events displayed as chat items | `Shared/Views/Chat/ChatItem/CICallItemView.swift` |
| Incoming call view | Dedicated UI for incoming call notifications | `Shared/Views/Call/IncomingCallView.swift` |
-### 5. Privacy & Security
+### 5. Your privacy
Encryption, authentication, and privacy controls.
diff --git a/apps/ios/product/views/settings.md b/apps/ios/product/views/settings.md
index 3cc4da5d2b..7e7f653910 100644
--- a/apps/ios/product/views/settings.md
+++ b/apps/ios/product/views/settings.md
@@ -4,7 +4,7 @@
## Purpose
-Configure all aspects of app behavior including notifications, network/servers, privacy, appearance, database management, call settings, and developer tools. Accessed from the UserPicker sheet on the chat list.
+Configure all aspects of app behavior including notifications, network/servers, privacy, appearance, database management, call settings, and Developer. Accessed from the UserPicker sheet on the chat list.
## Route / Navigation
@@ -22,7 +22,7 @@ Configure all aspects of app behavior including notifications, network/servers,
| Notifications | `bolt` (color varies by token status) | `NotificationsView` | Push notification mode and preview settings |
| Network & servers | `externaldrive.connected.to.line.below` | `NetworkAndServers` | SMP/XFTP servers, proxy, .onion hosts, advanced network |
| Audio & video calls | `video` | `CallSettings` | WebRTC relay policy, ICE servers, CallKit options |
-| Privacy & security | `lock` | `PrivacySettings` | SimpleX Lock, screen protection, delivery receipts, auto-accept |
+| Your privacy | `lock` | `PrivacySettings` | SimpleX Lock, screen protection, delivery receipts, auto-accept |
| Appearance | `sun.max` | `AppearanceSettings` | Theme, language, wallpapers, chat bubbles, toolbar opacity |
All rows disabled when `chatModel.chatRunning != true`. Appearance row only shown when `UIApplication.shared.supportsAlternateIcons`.
@@ -77,7 +77,7 @@ Adding a relay: `NewChatRelayView` form with name, address, test, and enable tog
Server validation (`validateServers_`) now returns both errors and warnings.
-#### Privacy & Security (`PrivacySettings`)
+#### Your privacy (`PrivacySettings`)
| Setting | Description |
|---|---|
@@ -152,7 +152,7 @@ Database row shows exclamation octagon icon in red when `chatRunning == false`.
| Row | Icon | Destination | Description |
|---|---|---|---|
-| Developer tools | `chevron.left.forwardslash.chevron.right` | `DeveloperView` | Chat console/terminal, log level, confirm DB upgrades |
+| Developer | `chevron.left.forwardslash.chevron.right` | `DeveloperView` | Chat console/terminal, log level, confirm DB upgrades |
| App version | (none) | `VersionView` | Shows "v{version} ({build})" |
## Loading / Error States
diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings
index 9f79b5dea0..042359dd7e 100644
--- a/apps/ios/ru.lproj/Localizable.strings
+++ b/apps/ios/ru.lproj/Localizable.strings
@@ -2064,7 +2064,7 @@ alert button */
"Developer options" = "Опции разработчика";
/* No comment provided by engineer. */
-"Developer tools" = "Инструменты разработчика";
+"Developer" = "Разработчик";
/* No comment provided by engineer. */
"Device" = "Устройство";
@@ -4533,7 +4533,7 @@ alert button */
"Previously connected servers" = "Ранее подключенные серверы";
/* No comment provided by engineer. */
-"Privacy & security" = "Конфиденциальность";
+"Your privacy" = "Конфиденциальность";
/* No comment provided by engineer. */
"Privacy for your customers." = "Конфиденциальность для ваших покупателей.";
@@ -6759,7 +6759,7 @@ server test failure */
"You can enable later via Settings" = "Вы можете включить их позже в Настройках";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Вы можете включить их позже в настройках Конфиденциальности.";
+"You can enable them later via app Your privacy settings." = "Вы можете включить их позже в настройках Конфиденциальности.";
/* No comment provided by engineer. */
"You can give another try." = "Вы можете попробовать ещё раз.";
diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings
index cc3abea189..62bf4d3d59 100644
--- a/apps/ios/th.lproj/Localizable.strings
+++ b/apps/ios/th.lproj/Localizable.strings
@@ -946,7 +946,7 @@ alert button */
"Develop" = "พัฒนา";
/* No comment provided by engineer. */
-"Developer tools" = "เครื่องมือสำหรับนักพัฒนา";
+"Developer" = "เครื่องมือสำหรับนักพัฒนา";
/* No comment provided by engineer. */
"Device" = "อุปกรณ์";
@@ -2172,7 +2172,7 @@ new chat action */
"Preview" = "ดูตัวอย่าง";
/* No comment provided by engineer. */
-"Privacy & security" = "ความเป็นส่วนตัวและความปลอดภัย";
+"Your privacy" = "ความเป็นส่วนตัวและความปลอดภัย";
/* No comment provided by engineer. */
"Private filenames" = "ชื่อไฟล์ส่วนตัว";
@@ -3089,7 +3089,7 @@ server test failure */
"You can enable later via Settings" = "คุณสามารถเปิดใช้งานในภายหลังผ่านการตั้งค่า";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "คุณสามารถเปิดใช้งานได้ในภายหลังผ่านการตั้งค่าความเป็นส่วนตัวและความปลอดภัยของแอป";
+"You can enable them later via app Your privacy settings." = "คุณสามารถเปิดใช้งานได้ในภายหลังผ่านการตั้งค่าความเป็นส่วนตัวและความปลอดภัยของแอป";
/* No comment provided by engineer. */
"You can hide or mute a user profile - swipe it to the right." = "คุณสามารถซ่อนหรือปิดเสียงโปรไฟล์ผู้ใช้ - ปัดไปทางขวา";
diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings
index e06989afee..8dfc23078c 100644
--- a/apps/ios/tr.lproj/Localizable.strings
+++ b/apps/ios/tr.lproj/Localizable.strings
@@ -1859,7 +1859,7 @@ alert button */
"Developer options" = "Geliştirici seçenekleri";
/* No comment provided by engineer. */
-"Developer tools" = "Geliştirici araçları";
+"Developer" = "Geliştirici araçları";
/* No comment provided by engineer. */
"Device" = "Cihaz";
@@ -4099,7 +4099,7 @@ alert button */
"Previously connected servers" = "Önceden bağlanılmış sunucular";
/* No comment provided by engineer. */
-"Privacy & security" = "Gizlilik & güvenlik";
+"Your privacy" = "Gizlilik & güvenlik";
/* No comment provided by engineer. */
"Privacy for your customers." = "Müşterileriniz için gizlilik.";
@@ -6064,7 +6064,7 @@ server test failure */
"You can enable later via Settings" = "Daha sonra Ayarlardan etkinleştirebilirsin";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Daha sonra uygulamanın Gizlilik ve Güvenlik ayarlarından etkinleştirebilirsiniz.";
+"You can enable them later via app Your privacy settings." = "Daha sonra uygulamanın Gizlilik ve Güvenlik ayarlarından etkinleştirebilirsiniz.";
/* No comment provided by engineer. */
"You can give another try." = "Bir kez daha deneyebilirsiniz.";
diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings
index 4a21eb4ae8..ff2f64e2ed 100644
--- a/apps/ios/uk.lproj/Localizable.strings
+++ b/apps/ios/uk.lproj/Localizable.strings
@@ -1806,7 +1806,7 @@ alert button */
"Developer options" = "Можливості для розробників";
/* No comment provided by engineer. */
-"Developer tools" = "Інструменти для розробників";
+"Developer" = "Інструменти для розробників";
/* No comment provided by engineer. */
"Device" = "Пристрій";
@@ -4019,7 +4019,7 @@ alert button */
"Previously connected servers" = "Раніше підключені сервери";
/* No comment provided by engineer. */
-"Privacy & security" = "Конфіденційність і безпека";
+"Your privacy" = "Конфіденційність і безпека";
/* No comment provided by engineer. */
"Privacy for your customers." = "Конфіденційність для ваших клієнтів.";
@@ -5966,7 +5966,7 @@ server test failure */
"You can enable later via Settings" = "Ви можете увімкнути пізніше в Налаштуваннях";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "Ви можете увімкнути їх пізніше в налаштуваннях конфіденційності та безпеки програми.";
+"You can enable them later via app Your privacy settings." = "Ви можете увімкнути їх пізніше в налаштуваннях конфіденційності та безпеки програми.";
/* No comment provided by engineer. */
"You can give another try." = "Ви можете спробувати ще раз.";
diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings
index 13be5125ea..f3138230f9 100644
--- a/apps/ios/zh-Hans.lproj/Localizable.strings
+++ b/apps/ios/zh-Hans.lproj/Localizable.strings
@@ -1836,7 +1836,7 @@ alert button */
"Developer options" = "开发者选项";
/* No comment provided by engineer. */
-"Developer tools" = "开发者工具";
+"Developer" = "开发者工具";
/* No comment provided by engineer. */
"Device" = "设备";
@@ -4107,7 +4107,7 @@ alert button */
"Previously connected servers" = "以前连接的服务器";
/* No comment provided by engineer. */
-"Privacy & security" = "隐私和安全";
+"Your privacy" = "隐私和安全";
/* No comment provided by engineer. */
"Privacy for your customers." = "客户隐私。";
@@ -6093,7 +6093,7 @@ server test failure */
"You can enable later via Settings" = "您可以稍后在设置中启用它";
/* No comment provided by engineer. */
-"You can enable them later via app Privacy & Security settings." = "您可以稍后通过应用程序的 \"隐私与安全 \"设置启用它们。";
+"You can enable them later via app Your privacy settings." = "您可以稍后通过应用程序的 \"隐私与安全 \"设置启用它们。";
/* No comment provided by engineer. */
"You can give another try." = "你可以再试一次。";
diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt
index 04b59732dd..5e9706d713 100644
--- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt
+++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt
@@ -1,7 +1,15 @@
package chat.simplex.common.views.usersettings
+import SectionItemView
import SectionView
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
import chat.simplex.common.model.ChatModel
import chat.simplex.common.platform.*
import chat.simplex.common.views.helpers.*
@@ -11,19 +19,19 @@ import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
@Composable
-actual fun SettingsSectionApp(
+actual fun AdvancedSettingsAppSection(
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
- showVersion: () -> Unit,
- withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
+ withAuth: (title: String, desc: String, block: () -> Unit) -> Unit,
) {
- SectionView(stringResource(MR.strings.settings_section_title_app)) {
- SettingsActionItem(painterResource(MR.images.ic_restart_alt), stringResource(MR.strings.settings_restart_app), ::restartApp)
- SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), { shutdownAppAlert(::shutdownApp) })
+ SectionView {
SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(withAuth) })
- AppVersionItem(showVersion)
}
}
+@Composable
+actual fun AppShutdownItem() {
+ SettingsActionItem(painterResource(MR.images.ic_power_settings_new), stringResource(MR.strings.settings_shutdown), ::shutdownAppAlert)
+}
fun restartApp() {
ProcessPhoenix.triggerRebirth(androidAppContext)
@@ -36,11 +44,28 @@ private fun shutdownApp() {
Runtime.getRuntime().exit(0)
}
-private fun shutdownAppAlert(onConfirm: () -> Unit) {
- AlertManager.shared.showAlertDialog(
+private fun shutdownAppAlert() {
+ AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.shutdown_alert_question),
text = generalGetString(MR.strings.shutdown_alert_desc),
- destructive = true,
- onConfirm = onConfirm
+ buttons = {
+ Column {
+ SectionItemView({ AlertManager.shared.hideAlert() }) {
+ Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
+ }
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ restartApp()
+ }) {
+ Text(stringResource(MR.strings.settings_restart_app), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
+ }
+ SectionItemView({
+ AlertManager.shared.hideAlert()
+ shutdownApp()
+ }) {
+ Text(stringResource(MR.strings.settings_shutdown), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red)
+ }
+ }
+ }
)
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt
index dce1b6ea33..13be8b1d71 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt
@@ -248,6 +248,8 @@ fun deleteContactDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? =
private fun deleteContactOrConversationDialog(chat: Chat, contact: Contact, chatModel: ChatModel, close: (() -> Unit)?) {
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.delete_contact_question),
+ text = contact.displayName,
+ parseHtml = false,
buttons = {
Column {
// Only delete conversation
@@ -306,7 +308,8 @@ private fun deleteActiveContactDialog(chat: Chat, contact: Contact, chatModel: C
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.delete_contact_question),
- text = generalGetString(MR.strings.delete_contact_cannot_undo_warning),
+ text = "${contact.displayName}\n\n${generalGetString(MR.strings.delete_contact_cannot_undo_warning)}",
+ parseHtml = false,
buttons = {
Column {
// Keep conversation toggle
@@ -361,7 +364,8 @@ private fun deleteActiveContactDialog(chat: Chat, contact: Contact, chatModel: C
private fun deleteContactWithoutConversation(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?) {
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.confirm_delete_contact_question),
- text = generalGetString(MR.strings.delete_contact_cannot_undo_warning),
+ text = "${chat.chatInfo.displayName}\n\n${generalGetString(MR.strings.delete_contact_cannot_undo_warning)}",
+ parseHtml = false,
buttons = {
Column {
// Delete and notify contact
@@ -417,7 +421,8 @@ private fun deleteContactWithoutConversation(chat: Chat, chatModel: ChatModel, c
private fun deleteNotReadyContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?) {
AlertManager.shared.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.confirm_delete_contact_question),
- text = generalGetString(MR.strings.delete_contact_cannot_undo_warning),
+ text = "${chat.chatInfo.displayName}\n\n${generalGetString(MR.strings.delete_contact_cannot_undo_warning)}",
+ parseHtml = false,
buttons = {
// Confirm
SectionItemView({
@@ -492,7 +497,8 @@ fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, chatDe
fun clearChatDialog(chat: Chat, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.clear_chat_question),
- text = generalGetString(MR.strings.clear_chat_warning),
+ text = "${chat.chatInfo.displayName}\n\n${generalGetString(MR.strings.clear_chat_warning)}",
+ parseHtml = false,
confirmText = generalGetString(MR.strings.clear_verb),
onConfirm = { controller.clearChat(chat, close) },
destructive = true,
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 d874079238..26824cdd49 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
@@ -288,16 +288,22 @@ expect fun AttachmentSelection(
)
fun MutableState.onFilesAttached(uris: List) {
- val groups = uris.groupBy { isImage(it) }
- val images = groups[true] ?: emptyList()
+ val groups = uris.groupBy { isImage(it) || isVideoUri(it) }
+ val media = groups[true] ?: emptyList()
val files = groups[false] ?: emptyList()
- if (images.isNotEmpty()) {
- CoroutineScope(Dispatchers.IO).launch { processPickedMedia(images, null) }
+ if (media.isNotEmpty()) {
+ CoroutineScope(Dispatchers.IO).launch { processPickedMedia(media, null) }
} else if (files.isNotEmpty()) {
processPickedFile(uris.first(), null)
}
}
+private fun isVideoUri(uri: URI): Boolean {
+ val name = getFileName(uri)?.lowercase() ?: return false
+ return name.endsWith(".mov") || name.endsWith(".avi") || name.endsWith(".mp4") ||
+ name.endsWith(".mpg") || name.endsWith(".mpeg") || name.endsWith(".mkv")
+}
+
fun MutableState.processPickedFile(uri: URI?, text: String?) {
if (uri != null) {
val fileSize = getFileSize(uri)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
index 3e9b5f6f48..c88e3b2bff 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
@@ -199,7 +199,8 @@ fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, cl
}
AlertManager.shared.showAlertDialog(
title = generalGetString(titleId),
- text = generalGetString(messageId),
+ text = "${groupInfo.displayName}\n\n${generalGetString(messageId)}",
+ parseHtml = false,
confirmText = generalGetString(MR.strings.delete_verb),
onConfirm = {
withBGApi {
@@ -233,7 +234,8 @@ fun leaveGroupDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl
MR.strings.you_will_stop_receiving_messages_from_this_chat_chat_history_will_be_preserved
AlertManager.shared.showAlertDialog(
title = generalGetString(titleId),
- text = generalGetString(messageId),
+ text = "${groupInfo.displayName}\n\n${generalGetString(messageId)}",
+ parseHtml = false,
confirmText = generalGetString(MR.strings.leave_group_button),
onConfirm = {
withLongRunningApi(60_000) {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
index 64288d9055..b4abbe70a2 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
@@ -12,8 +12,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.*
import androidx.compose.ui.graphics.*
+import androidx.compose.ui.graphics.drawscope.clipPath
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.*
@@ -1224,12 +1226,25 @@ fun Modifier.clipChatItem(chatItem: ChatItem? = null, tailVisible: Boolean = fal
val style = shapeStyle(chatItem, chatItemTail.value, tailVisible, revealed)
val cornerRoundness = chatItemRoundness.value.coerceIn(0f, 1f)
- val shape = when (style) {
- is ShapeStyle.Bubble -> chatItemShape(cornerRoundness, LocalDensity.current, style.tailVisible, chatItem?.chatDir?.sent == true)
- is ShapeStyle.RoundRect -> RoundedCornerShape(style.radius * cornerRoundness)
+ return when (style) {
+ is ShapeStyle.Bubble -> {
+ // Modifier.clip of the bubble GenericShape mis-hit-tests its path on very tall
+ // items, dropping long-press on the lower part of the bubble (issue #6991). Clip
+ // in the draw pass instead — drawing is clipped identically (the press ripple
+ // included), with no effect on hit-test.
+ val shape = chatItemShape(cornerRoundness, LocalDensity.current, style.tailVisible, chatItem?.chatDir?.sent == true)
+ this.drawWithCache {
+ val path = Path().apply {
+ addOutline(shape.createOutline(size, layoutDirection, this@drawWithCache))
+ }
+ onDrawWithContent {
+ clipPath(path) { this@onDrawWithContent.drawContent() }
+ }
+ }
+ }
+ // RoundRect hit-tests correctly — no bug here, keep the antialiased Modifier.clip.
+ is ShapeStyle.RoundRect -> this.clip(RoundedCornerShape(style.radius * cornerRoundness))
}
-
- return this.clip(shape)
}
private fun chatItemShape(roundness: Float, density: Density, tailVisible: Boolean, sent: Boolean = false): GenericShape = GenericShape { size, _ ->
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
index d3533bbd02..7dfb52063e 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
@@ -772,10 +772,11 @@ fun rejectContactRequest(rhId: Long?, contactRequestId: Long, chatModel: ChatMod
fun deleteContactConnectionAlert(rhId: Long?, connection: PendingContactConnection, chatModel: ChatModel, onSuccess: () -> Unit) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.delete_pending_connection__question),
- text = generalGetString(
+ text = "${connection.displayName}\n\n" + generalGetString(
if (connection.initiated) MR.strings.contact_you_shared_link_with_wont_be_able_to_connect
else MR.strings.connection_you_accepted_will_be_cancelled
),
+ parseHtml = false,
confirmText = generalGetString(MR.strings.delete_verb),
onConfirm = {
withBGApi {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt
index 648a0eb8e7..80f97d1caf 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt
@@ -44,29 +44,8 @@ fun DatabaseView() {
val prefs = m.controller.appPrefs
val useKeychain = remember { mutableStateOf(prefs.storeDBPassphrase.get()) }
val chatLastStart = remember { mutableStateOf(prefs.chatLastStart.get()) }
- val chatArchiveFile = remember { mutableStateOf(null) }
val stopped = remember { m.chatRunning }.value == false
- val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? ->
- val archive = chatArchiveFile.value
- if (archive != null && to != null) {
- copyFileToFile(File(archive), to) {}
- }
- // delete no matter the database was exported or canceled the export process
- if (archive != null) {
- File(archive).delete()
- chatArchiveFile.value = null
- }
- }
val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(appFilesDir.absolutePath)) }
- val importArchiveLauncher = rememberFileChooserLauncher(true) { to: URI? ->
- if (to != null) {
- importArchiveAlert {
- stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) {
- importArchive(to, appFilesCountAndSize, progressIndicator, false)
- }
- }
- }
- }
val chatItemTTL = remember { mutableStateOf(m.chatItemTTL.value) }
Box(
Modifier.fillMaxSize(),
@@ -79,27 +58,10 @@ fun DatabaseView() {
useKeychain.value,
m.chatDbEncrypted.value,
m.controller.appPrefs.storeDBPassphrase.state.value,
- m.controller.appPrefs.initialRandomDBPassphrase,
- importArchiveLauncher,
appFilesCountAndSize,
chatItemTTL,
user,
m.users,
- startChat = { startChat(m, chatLastStart, m.chatDbChanged, progressIndicator) },
- stopChatAlert = { stopChatAlert(m, progressIndicator) },
- exportArchive = {
- stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) {
- exportArchive(m, progressIndicator, chatArchiveFile, saveArchiveLauncher)
- }
- },
- deleteChatAlert = {
- deleteChatAlert {
- stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) {
- deleteChat(m, progressIndicator)
- true
- }
- }
- },
deleteAppFilesAndMedia = {
deleteFilesAndMediaAlert {
stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) {
@@ -120,12 +82,9 @@ fun DatabaseView() {
setCiTTL(m, rhId, chatItemTTL, progressIndicator, appFilesCountAndSize)
}
},
- disconnectAllHosts = {
- val connected = chatModel.remoteHosts.filter { it.sessionState is RemoteHostSessionState.Connected }
- connected.forEachIndexed { index, h ->
- controller.stopRemoteHostAndReloadHosts(h, index == connected.lastIndex && chatModel.connectedToRemote())
- }
- }
+ showDatabaseManagement = {
+ ModalManager.start.showModal(cardScreen = true) { DatabaseManagementView() }
+ },
)
if (progressIndicator.value) {
Box(
@@ -151,24 +110,18 @@ fun DatabaseLayout(
useKeyChain: Boolean,
chatDbEncrypted: Boolean?,
passphraseSaved: Boolean,
- initialRandomDBPassphrase: SharedPreference,
- importArchiveLauncher: FileChooserLauncher,
appFilesCountAndSize: MutableState>,
chatItemTTL: MutableState,
currentUser: User?,
users: List,
- startChat: () -> Unit,
- stopChatAlert: () -> Unit,
- exportArchive: () -> Unit,
- deleteChatAlert: () -> Unit,
deleteAppFilesAndMedia: () -> Unit,
onChatItemTTLSelected: (ChatItemTTL?) -> Unit,
- disconnectAllHosts: () -> Unit,
+ showDatabaseManagement: () -> Unit,
) {
val operationsDisabled = progressIndicator && !chatModel.desktopNoUserNoRemote
ColumnWithScrollBar {
- AppBarTitle(stringResource(MR.strings.your_chat_database))
+ AppBarTitle(stringResource(MR.strings.chat_data))
if (!chatModel.desktopNoUserNoRemote) {
SectionView(stringResource(MR.strings.messages_section_title)) {
@@ -187,79 +140,17 @@ fun DatabaseLayout(
)
SectionDividerSpaced()
}
- val toggleEnabled = remember { chatModel.remoteHosts }.none { it.sessionState is RemoteHostSessionState.Connected }
- if (chatModel.localUserCreated.value == true) {
- // still show the toggle in case database was stopped when the user opened this screen because it can be in the following situations:
- // - database was stopped after migration and the app relaunched
- // - something wrong happened with database operations and the database couldn't be launched when it should
- SectionView(stringResource(MR.strings.run_chat_section)) {
- if (!toggleEnabled) {
- SectionItemView(disconnectAllHosts) {
- Text(generalGetString(MR.strings.disconnect_remote_hosts), Modifier.fillMaxWidth(), color = WarningOrange)
- }
- }
- RunChatSetting(stopped, toggleEnabled && !progressIndicator, startChat, stopChatAlert)
- }
- if (stopped) SectionTextFooter(stringResource(MR.strings.you_must_use_the_most_recent_version_of_database))
- SectionDividerSpaced()
- }
- SectionView(stringResource(MR.strings.chat_database_section)) {
- if (chatModel.localUserCreated.value != true && !toggleEnabled) {
- SectionItemView(disconnectAllHosts) {
- Text(generalGetString(MR.strings.disconnect_remote_hosts), Modifier.fillMaxWidth(), color = WarningOrange)
- }
- }
+ SectionView {
val unencrypted = chatDbEncrypted == false
SettingsActionItem(
if (unencrypted) painterResource(MR.images.ic_lock_open_right) else if (useKeyChain) painterResource(MR.images.ic_vpn_key_filled)
else painterResource(MR.images.ic_lock),
- stringResource(MR.strings.database_passphrase),
- click = { ModalManager.start.showModal(cardScreen = true) { DatabaseEncryptionView(chatModel, false) } },
+ stringResource(MR.strings.database_passphrase_and_export),
+ click = showDatabaseManagement,
iconColor = if (unencrypted || (appPlatform.isDesktop && passphraseSaved)) WarningOrange else MaterialTheme.colors.secondary,
disabled = operationsDisabled
)
- if (appPlatform.isDesktop) {
- SettingsActionItem(
- painterResource(MR.images.ic_folder_open),
- stringResource(MR.strings.open_database_folder),
- ::desktopOpenDatabaseDir,
- disabled = operationsDisabled
- )
- }
- SettingsActionItem(
- painterResource(MR.images.ic_ios_share),
- stringResource(MR.strings.export_database),
- click = {
- if (initialRandomDBPassphrase.get()) {
- exportProhibitedAlert()
- ModalManager.start.showModal {
- DatabaseEncryptionView(chatModel, false)
- }
- } else {
- exportArchive()
- }
- },
- textColor = MaterialTheme.colors.primary,
- iconColor = MaterialTheme.colors.primary,
- disabled = operationsDisabled
- )
- SettingsActionItem(
- painterResource(MR.images.ic_download),
- stringResource(MR.strings.import_database),
- { withLongRunningApi { importArchiveLauncher.launch("application/zip") } },
- textColor = Color.Red,
- iconColor = Color.Red,
- disabled = operationsDisabled
- )
- SettingsActionItem(
- painterResource(MR.images.ic_delete_forever),
- stringResource(MR.strings.delete_database),
- deleteChatAlert,
- textColor = Color.Red,
- iconColor = Color.Red,
- disabled = operationsDisabled
- )
}
SectionDividerSpaced()
@@ -287,6 +178,155 @@ fun DatabaseLayout(
}
}
+@Composable
+fun DatabaseManagementView() {
+ val m = chatModel
+ val progressIndicator = remember { mutableStateOf(false) }
+ val prefs = m.controller.appPrefs
+ val useKeychain = remember { mutableStateOf(prefs.storeDBPassphrase.get()) }
+ val chatLastStart = remember { mutableStateOf(prefs.chatLastStart.get()) }
+ val chatArchiveFile = remember { mutableStateOf(null) }
+ val stopped = remember { m.chatRunning }.value == false
+ val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? ->
+ val archive = chatArchiveFile.value
+ if (archive != null && to != null) {
+ copyFileToFile(File(archive), to) {}
+ }
+ // delete no matter the database was exported or canceled the export process
+ if (archive != null) {
+ File(archive).delete()
+ chatArchiveFile.value = null
+ }
+ }
+ val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(appFilesDir.absolutePath)) }
+ val importArchiveLauncher = rememberFileChooserLauncher(true) { to: URI? ->
+ if (to != null) {
+ importArchiveAlert {
+ stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) {
+ importArchive(to, appFilesCountAndSize, progressIndicator, false)
+ }
+ }
+ }
+ }
+ val operationsDisabled = progressIndicator.value && !m.desktopNoUserNoRemote
+
+ Box(Modifier.fillMaxSize()) {
+ ColumnWithScrollBar {
+ AppBarTitle(stringResource(MR.strings.database_passphrase_and_export))
+
+ val toggleEnabled = remember { chatModel.remoteHosts }.none { it.sessionState is RemoteHostSessionState.Connected }
+ val disconnectAllHosts = {
+ val connected = chatModel.remoteHosts.filter { it.sessionState is RemoteHostSessionState.Connected }
+ connected.forEachIndexed { index, h ->
+ controller.stopRemoteHostAndReloadHosts(h, index == connected.lastIndex && chatModel.connectedToRemote())
+ }
+ }
+ SectionView(stringResource(MR.strings.chat_database_section)) {
+ if (chatModel.localUserCreated.value != true && !toggleEnabled) {
+ SectionItemView(disconnectAllHosts) {
+ Text(generalGetString(MR.strings.disconnect_remote_hosts), Modifier.fillMaxWidth(), color = WarningOrange)
+ }
+ }
+ val unencrypted = m.chatDbEncrypted.value == false
+ SettingsActionItem(
+ if (unencrypted) painterResource(MR.images.ic_lock_open_right) else if (useKeychain.value) painterResource(MR.images.ic_vpn_key_filled)
+ else painterResource(MR.images.ic_lock),
+ stringResource(MR.strings.database_passphrase),
+ click = { ModalManager.start.showModal(cardScreen = true) { DatabaseEncryptionView(chatModel, false) } },
+ iconColor = if (unencrypted || (appPlatform.isDesktop && prefs.storeDBPassphrase.state.value)) WarningOrange else MaterialTheme.colors.secondary,
+ disabled = operationsDisabled
+ )
+ if (appPlatform.isDesktop) {
+ SettingsActionItem(
+ painterResource(MR.images.ic_folder_open),
+ stringResource(MR.strings.open_database_folder),
+ ::desktopOpenDatabaseDir,
+ disabled = operationsDisabled
+ )
+ }
+ SettingsActionItem(
+ painterResource(MR.images.ic_ios_share),
+ stringResource(MR.strings.export_database),
+ click = {
+ if (prefs.initialRandomDBPassphrase.get()) {
+ exportProhibitedAlert()
+ ModalManager.start.showModal {
+ DatabaseEncryptionView(chatModel, false)
+ }
+ } else {
+ stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) {
+ exportArchive(m, progressIndicator, chatArchiveFile, saveArchiveLauncher)
+ }
+ }
+ },
+ textColor = MaterialTheme.colors.primary,
+ iconColor = MaterialTheme.colors.primary,
+ disabled = operationsDisabled
+ )
+ SettingsActionItem(
+ painterResource(MR.images.ic_download),
+ stringResource(MR.strings.import_database),
+ { withLongRunningApi { importArchiveLauncher.launch("application/zip") } },
+ textColor = Color.Red,
+ iconColor = Color.Red,
+ disabled = operationsDisabled
+ )
+ SettingsActionItem(
+ painterResource(MR.images.ic_delete_forever),
+ stringResource(MR.strings.delete_database),
+ {
+ deleteChatAlert {
+ stopChatRunBlockStartChat(stopped, chatLastStart, progressIndicator) {
+ deleteChat(m, progressIndicator)
+ true
+ }
+ }
+ },
+ textColor = Color.Red,
+ iconColor = Color.Red,
+ disabled = operationsDisabled
+ )
+ }
+
+ if (chatModel.localUserCreated.value == true) {
+ SectionDividerSpaced()
+ // still show the toggle in case database was stopped when the user opened this screen because it can be in the following situations:
+ // - database was stopped after migration and the app relaunched
+ // - something wrong happened with database operations and the database couldn't be launched when it should
+ SectionView(stringResource(MR.strings.run_chat_section)) {
+ if (!toggleEnabled) {
+ SectionItemView(disconnectAllHosts) {
+ Text(generalGetString(MR.strings.disconnect_remote_hosts), Modifier.fillMaxWidth(), color = WarningOrange)
+ }
+ }
+ RunChatSetting(
+ stopped,
+ toggleEnabled && !progressIndicator.value,
+ startChat = { startChat(m, chatLastStart, m.chatDbChanged, progressIndicator) },
+ stopChatAlert = { stopChatAlert(m, progressIndicator) }
+ )
+ }
+ if (stopped) SectionTextFooter(stringResource(MR.strings.you_must_use_the_most_recent_version_of_database))
+ }
+ SectionBottomSpacer()
+ }
+ if (progressIndicator.value) {
+ Box(
+ Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator(
+ Modifier
+ .padding(horizontal = 2.dp)
+ .size(30.dp),
+ color = MaterialTheme.colors.secondary,
+ strokeWidth = 2.5.dp
+ )
+ }
+ }
+ }
+}
+
private fun setChatItemTTLAlert(
m: ChatModel, rhId: Long?, selectedChatItemTTL: MutableState,
progressIndicator: MutableState,
@@ -832,19 +872,13 @@ fun PreviewDatabaseLayout() {
useKeyChain = false,
chatDbEncrypted = false,
passphraseSaved = false,
- initialRandomDBPassphrase = SharedPreference({ true }, {}),
- importArchiveLauncher = rememberFileChooserLauncher(true) {},
appFilesCountAndSize = remember { mutableStateOf(0 to 0L) },
chatItemTTL = remember { mutableStateOf(ChatItemTTL.None) },
currentUser = User.sampleData,
users = listOf(UserInfo.sampleData),
- startChat = {},
- stopChatAlert = {},
- exportArchive = {},
- deleteChatAlert = {},
deleteAppFilesAndMedia = {},
onChatItemTTLSelected = {},
- disconnectAllHosts = {},
+ showDatabaseManagement = {},
)
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt
index 3d670d1c43..1774cf4394 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt
@@ -75,6 +75,8 @@ class AlertManager {
onDismissRequest: (() -> Unit)? = null,
hostDevice: Pair? = null,
belowTextContent: @Composable (() -> Unit) = {},
+ // When false, [text] is rendered as literal text — use for user-controlled content.
+ parseHtml: Boolean = true,
buttons: @Composable () -> Unit,
) {
showAlert {
@@ -82,8 +84,14 @@ class AlertManager {
onDismissRequest = { onDismissRequest?.invoke(); if (dismissible) hideAlert() },
title = alertTitle(title),
buttons = {
- AlertContent(text, hostDevice, extraPadding = true, textAlign = textAlign, belowTextContent = belowTextContent) {
- buttons()
+ if (parseHtml) {
+ AlertContent(text, hostDevice, extraPadding = true, textAlign = textAlign, belowTextContent = belowTextContent) {
+ buttons()
+ }
+ } else {
+ AlertContent(text?.let { AnnotatedString(it) }, hostDevice, extraPadding = true) {
+ buttons()
+ }
}
},
shape = RoundedCornerShape(corner = CornerSize(25.dp))
@@ -122,13 +130,15 @@ class AlertManager {
onDismissRequest: (() -> Unit)? = null,
destructive: Boolean = false,
hostDevice: Pair? = null,
+ // When false, [text] is rendered as literal text — use for user-controlled content.
+ parseHtml: Boolean = true,
) {
showAlert {
AlertDialog(
onDismissRequest = { onDismissRequest?.invoke(); hideAlert() },
title = alertTitle(title),
buttons = {
- AlertContent(text, hostDevice, true) {
+ val buttonRow: @Composable () -> Unit = {
Row(
Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING),
horizontalArrangement = Arrangement.SpaceBetween
@@ -149,6 +159,11 @@ class AlertManager {
}, Modifier.focusRequester(focusRequester)) { Text(confirmText, color = if (destructive) MaterialTheme.colors.error else Color.Unspecified) }
}
}
+ if (parseHtml) {
+ AlertContent(text, hostDevice, true, content = buttonRow)
+ } else {
+ AlertContent(text?.let { AnnotatedString(it) }, hostDevice, true, content = buttonRow)
+ }
},
shape = RoundedCornerShape(corner = CornerSize(25.dp))
)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt
index 424d500085..23c622bc34 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt
@@ -383,7 +383,7 @@ fun uniqueCombine(fileName: String, dir: File): String {
val ext = orig.extension
fun tryCombine(n: Int): String {
val suffix = if (n == 0) "" else "_$n"
- val f = "$name$suffix.$ext"
+ val f = if (ext.isEmpty()) "$name$suffix" else "$name$suffix.$ext"
return if (File(dir, f).exists()) tryCombine(n + 1) else f
}
return tryCombine(0)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt
index 150b2a38e0..91324bb39a 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt
@@ -24,43 +24,28 @@ import kotlin.collections.ArrayList
fun NotificationsSettingsView(
chatModel: ChatModel,
) {
- val onNotificationPreviewModeSelected = { mode: NotificationPreviewMode ->
- chatModel.controller.appPrefs.notificationPreviewMode.set(mode.name)
- chatModel.notificationPreviewMode.value = mode
- }
-
NotificationsSettingsLayout(
notificationsMode = remember { chatModel.controller.appPrefs.notificationsMode.state },
- notificationPreviewMode = chatModel.notificationPreviewMode,
- showPage = { page ->
+ showNotificationsMode = {
ModalManager.start.showModalCloseable(true) {
- when (page) {
- CurrentPage.NOTIFICATIONS_MODE -> NotificationsModeView(chatModel.controller.appPrefs.notificationsMode.state) { changeNotificationsMode(it, chatModel) }
- CurrentPage.NOTIFICATION_PREVIEW_MODE -> NotificationPreviewView(chatModel.notificationPreviewMode, onNotificationPreviewModeSelected)
- }
+ NotificationsModeView(chatModel.controller.appPrefs.notificationsMode.state) { changeNotificationsMode(it, chatModel) }
}
},
)
}
-enum class CurrentPage {
- NOTIFICATIONS_MODE, NOTIFICATION_PREVIEW_MODE
-}
-
@Composable
fun NotificationsSettingsLayout(
notificationsMode: State,
- notificationPreviewMode: State,
- showPage: (CurrentPage) -> Unit,
+ showNotificationsMode: () -> Unit,
) {
val modes = remember { notificationModes() }
- val previewModes = remember { notificationPreviewModes() }
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.notifications))
SectionView(null) {
if (appPlatform == AppPlatform.ANDROID) {
- SettingsActionItemWithContent(null, stringResource(MR.strings.settings_notifications_mode_title), { showPage(CurrentPage.NOTIFICATIONS_MODE) }) {
+ SettingsActionItemWithContent(null, stringResource(MR.strings.settings_notifications_mode_title), showNotificationsMode) {
Text(
modes.firstOrNull { it.value == notificationsMode.value }?.title ?: "",
maxLines = 1,
@@ -69,14 +54,6 @@ fun NotificationsSettingsLayout(
)
}
}
- SettingsActionItemWithContent(null, stringResource(MR.strings.settings_notification_preview_mode_title), { showPage(CurrentPage.NOTIFICATION_PREVIEW_MODE) }) {
- Text(
- previewModes.firstOrNull { it.value == notificationPreviewMode.value }?.title ?: "",
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- color = MaterialTheme.colors.secondary
- )
- }
}
if (platform.androidIsXiaomiDevice() && (notificationsMode.value == NotificationsMode.PERIODIC || notificationsMode.value == NotificationsMode.SERVICE)) {
SectionTextFooter(annotatedStringResource(MR.strings.xiaomi_ignore_battery_optimization))
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt
index 7316c9bd82..cf34fd5a44 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt
@@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.unit.dp
@@ -73,6 +74,48 @@ fun PrivacySettingsView(
stringResource(MR.strings.sanitize_links_toggle),
chatModel.controller.appPrefs.privacySanitizeLinks
)
+ }
+ SectionDividerSpaced()
+
+ SectionView(stringResource(MR.strings.settings_section_title_files)) {
+ SettingsPreferenceItem(painterResource(MR.images.ic_image), stringResource(MR.strings.auto_accept_images), chatModel.controller.appPrefs.privacyAcceptImages)
+ BlurRadiusOptions(remember { appPrefs.privacyMediaBlurRadius.state }) {
+ appPrefs.privacyMediaBlurRadius.set(it)
+ }
+ }
+
+ val currentUser = chatModel.currentUser.value
+ if (currentUser != null && !chatModel.desktopNoUserNoRemote) {
+ SectionDividerSpaced()
+ ContacRequestsFromGroupsSection(
+ currentUser = currentUser,
+ setAutoAcceptGrpDirectInvs = { enable ->
+ withApi {
+ chatModel.controller.apiSetUserAutoAcceptMemberContacts(currentUser, enable)
+ chatModel.currentUser.value = currentUser.copy(autoAcceptMemberContacts = enable)
+ }
+ }
+ )
+ }
+
+ SectionDividerSpaced()
+ SectionView {
+ SettingsActionItem(
+ painterResource(MR.images.ic_more_horiz),
+ stringResource(MR.strings.more_privacy),
+ showSettingsModal { MorePrivacyView(it) }
+ )
+ }
+ SectionBottomSpacer()
+ }
+}
+
+@Composable
+fun MorePrivacyView(chatModel: ChatModel) {
+ ColumnWithScrollBar {
+ AppBarTitle(stringResource(MR.strings.more_privacy))
+
+ SectionView(stringResource(MR.strings.settings_section_title_chats)) {
SettingsPreferenceItem(
painterResource(MR.images.ic_chat_bubble),
stringResource(MR.strings.privacy_show_last_messages),
@@ -98,10 +141,6 @@ fun PrivacySettingsView(
SettingsPreferenceItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.encrypt_local_files), chatModel.controller.appPrefs.privacyEncryptLocalFiles, onChange = { enable ->
withBGApi { chatModel.controller.apiSetEncryptLocalFiles(enable) }
})
- SettingsPreferenceItem(painterResource(MR.images.ic_image), stringResource(MR.strings.auto_accept_images), chatModel.controller.appPrefs.privacyAcceptImages)
- BlurRadiusOptions(remember { appPrefs.privacyMediaBlurRadius.state }) {
- appPrefs.privacyMediaBlurRadius.set(it)
- }
SettingsPreferenceItem(painterResource(MR.images.ic_security), stringResource(MR.strings.protect_ip_address), chatModel.controller.appPrefs.privacyAskToApproveRelays)
}
SectionTextFooter(
@@ -111,9 +150,34 @@ fun PrivacySettingsView(
stringResource(MR.strings.without_tor_or_vpn_ip_address_will_be_visible_to_file_servers)
}
)
+ SectionDividerSpaced()
+
+ SectionView(stringResource(MR.strings.notifications)) {
+ val previewModes = remember { notificationPreviewModes() }
+ val notificationPreviewMode = remember { chatModel.notificationPreviewMode }
+ SettingsActionItemWithContent(
+ painterResource(MR.images.ic_visibility_off),
+ stringResource(MR.strings.settings_notification_preview_mode_title),
+ click = {
+ ModalManager.start.showModalCloseable(true) {
+ NotificationPreviewView(notificationPreviewMode) { mode ->
+ chatModel.controller.appPrefs.notificationPreviewMode.set(mode.name)
+ chatModel.notificationPreviewMode.value = mode
+ }
+ }
+ }
+ ) {
+ Text(
+ previewModes.firstOrNull { it.value == notificationPreviewMode.value }?.title ?: "",
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ color = MaterialTheme.colors.secondary
+ )
+ }
+ }
val currentUser = chatModel.currentUser.value
- if (currentUser != null) {
+ if (currentUser != null && !chatModel.desktopNoUserNoRemote) {
fun setSendReceiptsContacts(enable: Boolean, clearOverrides: Boolean) {
withLongRunningApi(slow = 60_000) {
val mrs = UserMsgReceiptSettings(enable, clearOverrides)
@@ -164,57 +228,40 @@ fun PrivacySettingsView(
}
}
- fun setAutoAcceptGrpDirectInvs(enable: Boolean) {
- withApi {
- chatModel.controller.apiSetUserAutoAcceptMemberContacts(currentUser, enable)
- chatModel.currentUser.value = currentUser.copy(autoAcceptMemberContacts = enable)
+ SectionDividerSpaced()
+ DeliveryReceiptsSection(
+ currentUser = currentUser,
+ setOrAskSendReceiptsContacts = { enable ->
+ val contactReceiptsOverrides = chatModel.chats.value.fold(0) { count, chat ->
+ if (chat.chatInfo is ChatInfo.Direct) {
+ val sendRcpts = chat.chatInfo.contact.chatSettings.sendRcpts
+ count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
+ } else {
+ count
+ }
+ }
+ if (contactReceiptsOverrides == 0) {
+ setSendReceiptsContacts(enable, clearOverrides = false)
+ } else {
+ showUserContactsReceiptsAlert(enable, contactReceiptsOverrides, ::setSendReceiptsContacts)
+ }
+ },
+ setOrAskSendReceiptsGroups = { enable ->
+ val groupReceiptsOverrides = chatModel.chats.value.fold(0) { count, chat ->
+ if (chat.chatInfo is ChatInfo.Group) {
+ val sendRcpts = chat.chatInfo.groupInfo.chatSettings.sendRcpts
+ count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
+ } else {
+ count
+ }
+ }
+ if (groupReceiptsOverrides == 0) {
+ setSendReceiptsGroups(enable, clearOverrides = false)
+ } else {
+ showUserGroupsReceiptsAlert(enable, groupReceiptsOverrides, ::setSendReceiptsGroups)
+ }
}
- }
-
- if (!chatModel.desktopNoUserNoRemote) {
- SectionDividerSpaced()
- ContacRequestsFromGroupsSection(
- currentUser = currentUser,
- setAutoAcceptGrpDirectInvs = { enable ->
- setAutoAcceptGrpDirectInvs(enable)
- }
- )
-
- SectionDividerSpaced()
- DeliveryReceiptsSection(
- currentUser = currentUser,
- setOrAskSendReceiptsContacts = { enable ->
- val contactReceiptsOverrides = chatModel.chats.value.fold(0) { count, chat ->
- if (chat.chatInfo is ChatInfo.Direct) {
- val sendRcpts = chat.chatInfo.contact.chatSettings.sendRcpts
- count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
- } else {
- count
- }
- }
- if (contactReceiptsOverrides == 0) {
- setSendReceiptsContacts(enable, clearOverrides = false)
- } else {
- showUserContactsReceiptsAlert(enable, contactReceiptsOverrides, ::setSendReceiptsContacts)
- }
- },
- setOrAskSendReceiptsGroups = { enable ->
- val groupReceiptsOverrides = chatModel.chats.value.fold(0) { count, chat ->
- if (chat.chatInfo is ChatInfo.Group) {
- val sendRcpts = chat.chatInfo.groupInfo.chatSettings.sendRcpts
- count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
- } else {
- count
- }
- }
- if (groupReceiptsOverrides == 0) {
- setSendReceiptsGroups(enable, clearOverrides = false)
- } else {
- showUserGroupsReceiptsAlert(enable, groupReceiptsOverrides, ::setSendReceiptsGroups)
- }
- }
- )
- }
+ )
}
SectionBottomSpacer()
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt
index f17d3a6e4b..c0b822dbb4 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt
@@ -37,17 +37,15 @@ import chat.simplex.res.MR
@Composable
fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: () -> Unit) {
- val user = chatModel.currentUser.value
val stopped = chatModel.chatRunning.value == false
+ val showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit) = { modalView -> { ModalManager.start.showModal(settings = true, cardScreen = true) { modalView(chatModel) } } }
SettingsLayout(
stopped,
chatModel.chatDbEncrypted.value == true,
remember { chatModel.controller.appPrefs.storeDBPassphrase.state }.value,
- remember { chatModel.controller.appPrefs.notificationsMode.state },
- user?.displayName,
setPerformLA = setPerformLA,
showModal = { modalView -> { ModalManager.start.showModal { modalView(chatModel) } } },
- showSettingsModal = { modalView -> { ModalManager.start.showModal(settings = true, cardScreen = true) { modalView(chatModel) } } },
+ showSettingsModal = showSettingsModal,
showSettingsModalWithSearch = { modalView ->
ModalManager.start.showCustomModal { close ->
val search = rememberSaveable { mutableStateOf("") }
@@ -62,12 +60,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: (
},
showCustomModal = { modalView -> { ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } } },
showVersion = {
- withBGApi {
- val info = chatModel.controller.apiGetVersion()
- if (info != null) {
- ModalManager.start.showModal { VersionInfoView(info) }
- }
- }
+ ModalManager.start.showModal(cardScreen = true) { VersionInfoView(showSettingsModal, ::doWithAuth) }
},
withAuth = ::doWithAuth,
)
@@ -84,8 +77,6 @@ fun SettingsLayout(
stopped: Boolean,
encrypted: Boolean,
passphraseSaved: Boolean,
- notificationsMode: State,
- userDisplayName: String?,
setPerformLA: (Boolean) -> Unit,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
@@ -98,30 +89,52 @@ fun SettingsLayout(
LaunchedEffect(Unit) {
hideKeyboard(view)
}
- val uriHandler = LocalUriHandler.current
+ val notificationsMode = remember { chatModel.controller.appPrefs.notificationsMode.state }
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.your_settings))
- SectionView(stringResource(MR.strings.settings_section_title_settings)) {
- SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped)
- SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showCustomModal { _, close -> NetworkAndServersView(close) }, disabled = stopped)
- SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped)
- SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped)
+ SectionView {
SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) })
- }
- SectionDividerSpaced()
-
- SectionView(stringResource(MR.strings.settings_section_title_chat_database)) {
+ SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.your_privacy), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped)
+ SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.help_and_support), showSettingsModal { HelpAndSupportView(it, showModal, showCustomModal) })
DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView() }, stopped)
SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped)
}
-
SectionDividerSpaced()
+ SectionView(stringResource(MR.strings.advanced_settings)) {
+ SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showCustomModal { _, close -> NetworkAndServersView(close) }, disabled = stopped)
+ if (appPlatform == AppPlatform.ANDROID) {
+ SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped)
+ }
+ SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped)
+ AppShutdownItem()
+ AppVersionItem(showVersion)
+ }
+ SectionBottomSpacer()
+ }
+}
+
+@Composable
+fun HelpAndSupportView(
+ chatModel: ChatModel,
+ showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
+ showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
+) {
+ val uriHandler = LocalUriHandler.current
+ val stopped = chatModel.chatRunning.value == false
+ val userDisplayName = chatModel.currentUser.value?.displayName ?: ""
+ ColumnWithScrollBar {
+ AppBarTitle(stringResource(MR.strings.help_and_support))
+
SectionView(stringResource(MR.strings.settings_section_title_help)) {
- SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped)
+ SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName) }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close = close) }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) })
+ }
+ SectionDividerSpaced()
+
+ SectionView(stringResource(MR.strings.settings_section_title_contact)) {
if (!chatModel.desktopNoUserNoRemote) {
SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped)
}
@@ -129,27 +142,29 @@ fun SettingsLayout(
}
SectionDividerSpaced()
- SectionView(stringResource(MR.strings.settings_section_title_support)) {
+ SectionView(stringResource(MR.strings.settings_section_title_support_project)) {
if (!BuildConfigCommon.ANDROID_BUNDLE) {
ContributeItem(uriHandler)
}
- RateAppItem(uriHandler)
+ if (appPlatform.isAndroid) {
+ RateAppItem(uriHandler)
+ }
StarOnGithubItem(uriHandler)
}
- SectionDividerSpaced()
-
- SettingsSectionApp(showSettingsModal, showVersion, withAuth)
SectionBottomSpacer()
}
}
@Composable
-expect fun SettingsSectionApp(
+expect fun AdvancedSettingsAppSection(
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
- showVersion: () -> Unit,
- withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
+ withAuth: (title: String, desc: String, block: () -> Unit) -> Unit,
)
+// Shutdown is only available on Android; on desktop the app is closed via the window.
+@Composable
+expect fun AppShutdownItem()
+
@Composable private fun DatabaseItem(encrypted: Boolean, saved: Boolean, openDatabaseView: () -> Unit, stopped: Boolean) {
SectionItemView(openDatabaseView) {
Row(
@@ -160,11 +175,11 @@ expect fun SettingsSectionApp(
Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
Icon(
painterResource(MR.images.ic_database),
- contentDescription = stringResource(MR.strings.database_passphrase_and_export),
+ contentDescription = stringResource(MR.strings.chat_data),
tint = if (encrypted && (appPlatform.isAndroid || !saved)) MaterialTheme.colors.secondary else WarningOrange,
)
TextIconSpaced(false)
- Text(stringResource(MR.strings.database_passphrase_and_export))
+ Text(stringResource(MR.strings.chat_data))
}
if (stopped) {
Icon(
@@ -208,7 +223,7 @@ fun ChatLockItem(
}
}
-@Composable private fun ContributeItem(uriHandler: UriHandler) {
+@Composable fun ContributeItem(uriHandler: UriHandler) {
SectionItemView({ uriHandler.openExternalLink("https://github.com/simplex-chat/simplex-chat#contribute") }) {
Icon(
painterResource(MR.images.ic_keyboard),
@@ -220,7 +235,7 @@ fun ChatLockItem(
}
}
-@Composable private fun RateAppItem(uriHandler: UriHandler) {
+@Composable fun RateAppItem(uriHandler: UriHandler) {
SectionItemView({
runCatching { uriHandler.openUriCatching("market://details?id=chat.simplex.app") }
.onFailure { uriHandler.openUriCatching("https://play.google.com/store/apps/details?id=chat.simplex.app") }
@@ -236,7 +251,7 @@ fun ChatLockItem(
}
}
-@Composable private fun StarOnGithubItem(uriHandler: UriHandler) {
+@Composable fun StarOnGithubItem(uriHandler: UriHandler) {
SectionItemView({ uriHandler.openExternalLink("https://github.com/simplex-chat/simplex-chat") }) {
Icon(
painter = painterResource(MR.images.ic_github),
@@ -486,8 +501,6 @@ fun PreviewSettingsLayout() {
stopped = false,
encrypted = false,
passphraseSaved = false,
- notificationsMode = remember { mutableStateOf(NotificationsMode.OFF) },
- userDisplayName = "Alice",
setPerformLA = { _ -> },
showModal = { {} },
showSettingsModal = { {} },
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt
index 52addd146b..5070c3c0aa 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/VersionInfoView.kt
@@ -1,33 +1,55 @@
package chat.simplex.common.views.usersettings
+import SectionBottomSpacer
+import SectionDividerSpaced
+import SectionView
+import itemHPadding
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.common.BuildConfigCommon
+import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.CoreVersionInfo
import chat.simplex.common.platform.ColumnWithScrollBar
import chat.simplex.common.platform.appPlatform
-import chat.simplex.common.ui.theme.DEFAULT_PADDING
+import chat.simplex.common.platform.chatModel
+import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF
import chat.simplex.common.views.helpers.AppBarTitle
import chat.simplex.res.MR
@Composable
-fun VersionInfoView(info: CoreVersionInfo) {
- ColumnWithScrollBar(
- Modifier.padding(horizontal = DEFAULT_PADDING),
- ) {
- AppBarTitle(stringResource(MR.strings.app_version_title), withPadding = false)
- if (appPlatform.isAndroid) {
- Text(String.format(stringResource(MR.strings.app_version_name), BuildConfigCommon.ANDROID_VERSION_NAME))
- Text(String.format(stringResource(MR.strings.app_version_code), BuildConfigCommon.ANDROID_VERSION_CODE))
- } else {
- Text(String.format(stringResource(MR.strings.app_version_name), BuildConfigCommon.DESKTOP_VERSION_NAME))
- Text(String.format(stringResource(MR.strings.app_version_code), BuildConfigCommon.DESKTOP_VERSION_CODE))
+fun VersionInfoView(
+ showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
+ withAuth: (title: String, desc: String, block: () -> Unit) -> Unit,
+) {
+ val versionInfo = remember { mutableStateOf(null) }
+ LaunchedEffect(Unit) {
+ versionInfo.value = chatModel.controller.apiGetVersion()
+ }
+ ColumnWithScrollBar {
+ AppBarTitle(stringResource(MR.strings.app_version_title))
+ SectionView {
+ Column(Modifier.padding(horizontal = itemHPadding, vertical = DEFAULT_PADDING_HALF)) {
+ if (appPlatform.isAndroid) {
+ Text(String.format(stringResource(MR.strings.app_version_name), BuildConfigCommon.ANDROID_VERSION_NAME))
+ Text(String.format(stringResource(MR.strings.app_version_code), BuildConfigCommon.ANDROID_VERSION_CODE))
+ } else {
+ Text(String.format(stringResource(MR.strings.app_version_name), BuildConfigCommon.DESKTOP_VERSION_NAME))
+ Text(String.format(stringResource(MR.strings.app_version_code), BuildConfigCommon.DESKTOP_VERSION_CODE))
+ }
+ versionInfo.value?.let { info ->
+ Text(String.format(stringResource(MR.strings.core_version), info.version))
+ val simplexmqCommit = if (info.simplexmqCommit.length >= 7) info.simplexmqCommit.substring(startIndex = 0, endIndex = 7) else info.simplexmqCommit
+ Text(String.format(stringResource(MR.strings.core_simplexmq_version), info.simplexmqVersion, simplexmqCommit))
+ }
+ }
}
- Text(String.format(stringResource(MR.strings.core_version), info.version))
- val simplexmqCommit = if (info.simplexmqCommit.length >= 7) info.simplexmqCommit.substring(startIndex = 0, endIndex = 7) else info.simplexmqCommit
- Text(String.format(stringResource(MR.strings.core_simplexmq_version), info.simplexmqVersion, simplexmqCommit))
+ SectionDividerSpaced()
+
+ AdvancedSettingsAppSection(showSettingsModal, withAuth)
+ SectionBottomSpacer()
}
}
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 ee79fc0af0..5bca9402a8 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -1555,6 +1555,13 @@
Files
Send delivery receipts to
Contact requests from groups
+ About
+ Contact
+ Support the project
+ Chat data
+ Help & support
+ More privacy
+ Advanced settings
Restart
Shutdown
Developer tools
@@ -2681,7 +2688,7 @@
Don\'t enable
You can enable later via Settings
Delivery receipts are disabled!
- You can enable them later via app Privacy & Security settings.
+ You can enable them later via app Your privacy settings.
Error enabling delivery receipts!
diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt
index 5b4a044df3..174ad63c7a 100644
--- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt
+++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt
@@ -1,27 +1,21 @@
package chat.simplex.common.views.usersettings
import SectionView
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.padding
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
+import androidx.compose.runtime.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel
-import chat.simplex.common.platform.AppUpdatesChannel
-import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF
+import chat.simplex.common.platform.*
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
@Composable
-actual fun SettingsSectionApp(
+actual fun AdvancedSettingsAppSection(
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
- showVersion: () -> Unit,
- withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
+ withAuth: (title: String, desc: String, block: () -> Unit) -> Unit,
) {
- SectionView(stringResource(MR.strings.settings_section_title_app)) {
+ SectionView {
SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(withAuth) })
val selectedChannel = remember { appPrefs.appUpdateChannel.state }
val values = AppUpdatesChannel.entries.map { it to it.text }
@@ -29,6 +23,8 @@ actual fun SettingsSectionApp(
appPrefs.appUpdateChannel.set(it)
setupUpdateChecker()
}
- AppVersionItem(showVersion)
}
}
+
+@Composable
+actual fun AppShutdownItem() {}
diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs
index 3d57fb2b43..53dedf1a91 100644
--- a/apps/simplex-directory-service/src/Directory/Service.hs
+++ b/apps/simplex-directory-service/src/Directory/Service.hs
@@ -204,7 +204,7 @@ linkCheckThread_ opts env@ServiceState {eventQ}
threadDelay $ linkCheckInterval opts * 1000000
u <- readTVarIO $ currentUser cc
forM_ u $ \user ->
- withDB' "linkCheckThread" cc (\db -> getAllGroupRegs_ db user) >>= \case
+ withDB' "linkCheckThread" cc (\db -> getAllGroupRegs_ db (storeCxt cc) user) >>= \case
Left e -> logError $ "linkCheckThread error: " <> T.pack e
Right grs -> forM_ grs $ \(gInfo, gr) ->
unless (groupRemoved $ groupRegStatus gr) $
@@ -462,7 +462,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
getOwnerGroupMember :: GroupId -> GroupReg -> IO (Either String GroupMember)
getOwnerGroupMember gId GroupReg {dbOwnerMemberId} = case dbOwnerMemberId of
- Just mId -> withDB "getGroupMember" cc $ \db -> withExceptT show $ getGroupMember db (vr cc) user gId mId
+ Just mId -> withDB "getGroupMember" cc $ \db -> withExceptT show $ getGroupMember db (storeCxt cc) user gId mId
Nothing -> pure $ Left "no owner member in group registration"
deServiceJoinedGroup :: ContactId -> GroupInfo -> GroupMember -> IO ()
@@ -556,7 +556,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
Right (CRConnectionPlan _ _ (CPGroupLink (GLPKnown {groupInfo = g'}))) ->
case dbOwnerMemberId gr of
Just ownerGMId ->
- withDB "getGroupMember" cc (\db -> withExceptT show $ getGroupMember db (vr cc) user groupId ownerGMId) >>= \case
+ withDB "getGroupMember" cc (\db -> withExceptT show $ getGroupMember db (storeCxt cc) user groupId ownerGMId) >>= \case
Right ownerMember
| let GroupMember {memberRole = role} = ownerMember, role >= GROwner ->
setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval n') (`updatedNotification` g')
@@ -813,7 +813,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
_ -> False
checkValidOwner dbOwnerMemberId owners onValid = case dbOwnerMemberId of
Just ownerGMId ->
- withDB "checkGroupLink" cc (\db -> withExceptT show $ getGroupMember db (vr cc) user groupId ownerGMId) >>= \case
+ withDB "checkGroupLink" cc (\db -> withExceptT show $ getGroupMember db (storeCxt cc) user groupId ownerGMId) >>= \case
Right GroupMember {memberId, memberPubKey}
| any (\GroupLinkOwner {memberId = mId, memberKey} -> memberId == mId && memberPubKey == Just memberKey) owners -> onValid
_ -> setGroupStatus logError st env cc groupId GRSSuspendedBadRoles $ \gr' ->
@@ -985,7 +985,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
addGroupReg notifyAdminUsers st cc ct gInfo GRSProposed $ \_ -> pure ()
sendChatCmd cc (APIConnectPreparedGroup gId False (Just ownerContact) Nothing) >>= \case
Right CRStartedConnectionToGroup {groupInfo = gInfo'} ->
- withDB "getGroupMember" cc (\db -> withExceptT show $ getGroupMemberByMemberId db (vr cc) user gInfo' mId) >>= \case
+ withDB "getGroupMember" cc (\db -> withExceptT show $ getGroupMemberByMemberId db (storeCxt cc) user gInfo' mId) >>= \case
Right ownerMember ->
void $ setGroupRegOwner cc gId ownerMember
Left e -> do
@@ -998,7 +998,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
deReregistration ct g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} profileChanged LinkOwnerSig {ownerId = Just (B64UrlByteString oIdBytes)} = do
let mId = MemberId oIdBytes
gt = maybe "group" groupTypeStr' pg_
- withDB "getGroupMemberByMemberId" cc (\db -> withExceptT show $ getGroupMemberByMemberId db (vr cc) user g mId) >>= \case
+ withDB "getGroupMemberByMemberId" cc (\db -> withExceptT show $ getGroupMemberByMemberId db (storeCxt cc) user g mId) >>= \case
Right ownerMember@GroupMember {memberRole = role, memberStatus} ->
if
| role >= GROwner && memberStatus /= GSMemUnknown ->
@@ -1451,7 +1451,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
getOwnersInfo :: [(GroupInfo, GroupReg)] -> IO [((GroupInfo, GroupReg), Maybe (Either String Contact))]
getOwnersInfo gs =
fmap (either (\e -> map (,Just (Left e)) gs) id) $ withDB' "getOwnersInfo" cc $ \db ->
- mapM (\g@(_, gr) -> fmap ((g,) . Just . first show) $ runExceptT $ getContact db (vr cc) user $ dbContactId gr) gs
+ mapM (\g@(_, gr) -> fmap ((g,) . Just . first show) $ runExceptT $ getContact db (storeCxt cc) user $ dbContactId gr) gs
sendGroupsInfo :: Contact -> ChatItemId -> Bool -> ([(GroupInfo, GroupReg)], Int) -> IO ()
sendGroupsInfo ct ciId isAdmin (gs, n) = do
@@ -1519,7 +1519,7 @@ updateGroupListingFiles cc u dir =
Left e -> logError $ "generateListing error: failed to read groups: " <> T.pack e
getContact' :: ChatController -> User -> ContactId -> IO (Either String Contact)
-getContact' cc user ctId = withDB "getContact" cc $ \db -> withExceptT show $ getContact db (vr cc) user ctId
+getContact' cc user ctId = withDB "getContact" cc $ \db -> withExceptT show $ getContact db (storeCxt cc) user ctId
getGroupLink' :: ChatController -> User -> GroupInfo -> IO (Either String GroupLink)
getGroupLink' cc user gInfo =
diff --git a/apps/simplex-directory-service/src/Directory/Store.hs b/apps/simplex-directory-service/src/Directory/Store.hs
index b5f7220724..4036bd8cf1 100644
--- a/apps/simplex-directory-service/src/Directory/Store.hs
+++ b/apps/simplex-directory-service/src/Directory/Store.hs
@@ -85,7 +85,6 @@ import Data.Time.Clock.System (systemEpochDay)
import Directory.Search
import Directory.Util
import Simplex.Chat.Controller
-import Simplex.Chat.Protocol (supportedChatVRange)
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
import Simplex.Chat.Store
import Simplex.Chat.Store.Groups
@@ -315,28 +314,28 @@ getGroupReg_ db gId =
getGroupAndReg :: ChatController -> User -> GroupId -> IO (Either String (GroupInfo, GroupReg))
getGroupAndReg cc user@User {userId, userContactId} gId =
withDB "getGroupAndReg" cc $ \db ->
- ExceptT $ firstRow (toGroupInfoReg (vr cc) user) ("group " ++ show gId ++ " not found") $
+ ExceptT $ firstRow (toGroupInfoReg (storeCxt cc) user) ("group " ++ show gId ++ " not found") $
DB.query db (groupReqQuery <> " AND g.group_id = ?") (userId, userContactId, gId)
getUserGroupReg :: ChatController -> User -> ContactId -> UserGroupRegId -> IO (Either String (GroupInfo, GroupReg))
getUserGroupReg cc user@User {userId, userContactId} ctId ugrId =
withDB "getUserGroupReg" cc $ \db ->
- ExceptT $ firstRow (toGroupInfoReg (vr cc) user) ("group " ++ show ugrId ++ " not found") $
+ ExceptT $ firstRow (toGroupInfoReg (storeCxt cc) user) ("group " ++ show ugrId ++ " not found") $
DB.query db (groupReqQuery <> " AND r.contact_id = ? AND r.user_group_reg_id = ?") (userId, userContactId, ctId, ugrId)
getUserGroupRegs :: ChatController -> User -> ContactId -> IO (Either String [(GroupInfo, GroupReg)])
getUserGroupRegs cc user@User {userId, userContactId} ctId =
withDB' "getUserGroupRegs" cc $ \db ->
- map (toGroupInfoReg (vr cc) user)
+ map (toGroupInfoReg (storeCxt cc) user)
<$> DB.query db (groupReqQuery <> " AND r.contact_id = ? ORDER BY r.user_group_reg_id") (userId, userContactId, ctId)
getAllListedGroups :: ChatController -> User -> IO (Either String [(GroupInfo, GroupReg, Maybe GroupLink)])
-getAllListedGroups cc user = withDB' "getAllListedGroups" cc $ \db -> getAllListedGroups_ db (vr cc) user
+getAllListedGroups cc user = withDB' "getAllListedGroups" cc $ \db -> getAllListedGroups_ db (storeCxt cc) user
-getAllListedGroups_ :: DB.Connection -> VersionRangeChat -> User -> IO [(GroupInfo, GroupReg, Maybe GroupLink)]
-getAllListedGroups_ db vr' user@User {userId, userContactId} =
+getAllListedGroups_ :: DB.Connection -> StoreCxt -> User -> IO [(GroupInfo, GroupReg, Maybe GroupLink)]
+getAllListedGroups_ db cxt user@User {userId, userContactId} =
DB.query db (groupReqQuery <> " AND r.group_reg_status = ?") (userId, userContactId, GRSActive)
- >>= mapM (withGroupLink . toGroupInfoReg vr' user)
+ >>= mapM (withGroupLink . toGroupInfoReg cxt user)
where
withGroupLink (g, gr) = (g,gr,) . eitherToMaybe <$> runExceptT (getGroupLink db user g)
@@ -382,7 +381,7 @@ searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pa
countQuery' = countQuery <> " JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id WHERE r.group_reg_status = ? "
orderBy = " ORDER BY g.summary_current_members_count DESC, r.group_reg_id ASC "
where
- groups = (map (toGroupInfoReg (vr cc) user) <$>)
+ groups = (map (toGroupInfoReg (storeCxt cc) user) <$>)
count = maybeFirstRow' 0 fromOnly
listedGroupQuery = groupReqQuery <> " AND r.group_reg_status = ? "
countQuery = "SELECT COUNT(1) FROM groups g JOIN sx_directory_group_regs r ON g.group_id = r.group_id "
@@ -395,22 +394,22 @@ searchListedGroups cc user@User {userId, userContactId} searchType lastGroup_ pa
)
|]
-getAllGroupRegs_ :: DB.Connection -> User -> IO [(GroupInfo, GroupReg)]
-getAllGroupRegs_ db user@User {userId, userContactId} =
- map (toGroupInfoReg supportedChatVRange user)
+getAllGroupRegs_ :: DB.Connection -> StoreCxt -> User -> IO [(GroupInfo, GroupReg)]
+getAllGroupRegs_ db cxt user@User {userId, userContactId} =
+ map (toGroupInfoReg cxt user)
<$> DB.query db groupReqQuery (userId, userContactId)
getDuplicateGroupRegs :: ChatController -> User -> Text -> IO (Either String [(GroupInfo, GroupReg)])
getDuplicateGroupRegs cc user@User {userId, userContactId} displayName =
withDB' "getDuplicateGroupRegs" cc $ \db ->
- map (toGroupInfoReg (vr cc) user)
+ map (toGroupInfoReg (storeCxt cc) user)
<$> DB.query db (groupReqQuery <> " AND gp.display_name = ?") (userId, userContactId, displayName)
listLastGroups :: ChatController -> User -> Int -> IO (Either String ([(GroupInfo, GroupReg)], Int))
listLastGroups cc user@User {userId, userContactId} count =
withDB' "getUserGroupRegs" cc $ \db -> do
gs <-
- map (toGroupInfoReg (vr cc) user)
+ map (toGroupInfoReg (storeCxt cc) user)
<$> DB.query db (groupReqQuery <> " ORDER BY group_reg_id DESC LIMIT ?") (userId, userContactId, count)
n <- maybeFirstRow' 0 fromOnly $ DB.query_ db "SELECT COUNT(1) FROM sx_directory_group_regs"
pure (gs, n)
@@ -419,14 +418,14 @@ listPendingGroups :: ChatController -> User -> Int -> IO (Either String ([(Group
listPendingGroups cc user@User {userId, userContactId} count =
withDB' "getUserGroupRegs" cc $ \db -> do
gs <-
- map (toGroupInfoReg (vr cc) user)
+ map (toGroupInfoReg (storeCxt cc) user)
<$> DB.query db (groupReqQuery <> " AND r.group_reg_status LIKE 'pending_approval%' ORDER BY group_reg_id DESC LIMIT ?") (userId, userContactId, count)
n <- maybeFirstRow' 0 fromOnly $ DB.query_ db "SELECT COUNT(1) FROM sx_directory_group_regs WHERE group_reg_status LIKE 'pending_approval%'"
pure (gs, n)
-toGroupInfoReg :: VersionRangeChat -> User -> (GroupInfoRow :. GroupRegRow) -> (GroupInfo, GroupReg)
-toGroupInfoReg vr' User {userContactId} (groupRow :. grRow) =
- (toGroupInfo vr' userContactId [] groupRow, rowToGroupReg grRow)
+toGroupInfoReg :: StoreCxt -> User -> (GroupInfoRow :. GroupRegRow) -> (GroupInfo, GroupReg)
+toGroupInfoReg cxt User {userContactId} (groupRow :. grRow) =
+ (toGroupInfo cxt userContactId [] groupRow, rowToGroupReg grRow)
type GroupRegRow = (GroupId, UserGroupRegId, ContactId, Maybe GroupMemberId, GroupRegStatus, BoolInt, UTCTime)
diff --git a/apps/simplex-directory-service/src/Directory/Store/Migrate.hs b/apps/simplex-directory-service/src/Directory/Store/Migrate.hs
index aa101d7bf7..d501fbd5c3 100644
--- a/apps/simplex-directory-service/src/Directory/Store/Migrate.hs
+++ b/apps/simplex-directory-service/src/Directory/Store/Migrate.hs
@@ -18,10 +18,9 @@ import Directory.Listing
import Directory.Options
import Directory.Store
import Simplex.Chat (createChatDatabase)
-import Simplex.Chat.Controller (ChatConfig (..), ChatDatabase (..))
+import Simplex.Chat.Controller (ChatConfig (..), ChatDatabase (..), mkStoreCxt)
import Simplex.Chat.Options (CoreChatOpts (..))
import Simplex.Chat.Options.DB
-import Simplex.Chat.Protocol (supportedChatVRange)
import Simplex.Chat.Store.Groups (getHostMember)
import Simplex.Chat.Store.Profiles (getUsers)
import Simplex.Chat.Store.Shared (getGroupInfo)
@@ -62,7 +61,7 @@ checkDirectoryLog opts cfg =
runDirectoryMigrations opts cfg st
gs <- readDirectoryLogData logFile
withActiveUser st $ \user -> withTransaction st $ \db -> do
- mapM_ (verifyGroupRegistration db user) gs
+ mapM_ (verifyGroupRegistration (mkStoreCxt cfg) db user) gs
putStrLn $ show (length gs) <> " group registrations OK"
importDirectoryLogToDB :: DirectoryOpts -> ChatConfig -> IO ()
@@ -73,7 +72,7 @@ importDirectoryLogToDB opts cfg = do
ctRegs <- TM.emptyIO
withActiveUser st $ \user -> withTransaction st $ \db -> do
forM_ gs $ \gr ->
- whenM (verifyGroupRegistration db user gr) $ do
+ whenM (verifyGroupRegistration (mkStoreCxt cfg) db user gr) $ do
putStrLn $ "importing group " <> show (dbGroupId gr)
insertGroupReg db =<< fixUserGroupRegId ctRegs gr
renamePath logFile (logFile ++ ".bak")
@@ -101,28 +100,28 @@ exportDBToDirectoryLog opts cfg =
runDirectoryMigrations opts cfg st
withActiveUser st $ \user -> do
gs <- withFile logFile WriteMode $ \h -> withTransaction st $ \db -> do
- gs <- getAllGroupRegs_ db user
+ gs <- getAllGroupRegs_ db (mkStoreCxt cfg) user
forM_ gs $ \(_, gr) ->
- whenM (verifyGroupRegistration db user gr) $
+ whenM (verifyGroupRegistration (mkStoreCxt cfg) db user gr) $
B.hPutStrLn h $ strEncode $ GRCreate gr
pure gs
putStrLn $ show (length gs) <> " group registrations exported"
saveGroupListingFiles :: DirectoryOpts -> ChatConfig -> IO ()
-saveGroupListingFiles opts _cfg = case webFolder opts of
+saveGroupListingFiles opts cfg = case webFolder opts of
Nothing -> exit "use --web-folder to generate listings"
Just dir ->
withChatStore opts $ \st -> withActiveUser st $ \user ->
withTransaction st $ \db ->
- getAllListedGroups_ db supportedChatVRange user >>= generateListing dir
+ getAllListedGroups_ db (mkStoreCxt cfg) user >>= generateListing dir
-verifyGroupRegistration :: DB.Connection -> User -> GroupReg -> IO Bool
-verifyGroupRegistration db user GroupReg {dbGroupId = gId, dbContactId = ctId, dbOwnerMemberId, groupRegStatus} =
- runExceptT (getGroupInfo db supportedChatVRange user gId) >>= \case
+verifyGroupRegistration :: StoreCxt -> DB.Connection -> User -> GroupReg -> IO Bool
+verifyGroupRegistration cxt db user GroupReg {dbGroupId = gId, dbContactId = ctId, dbOwnerMemberId, groupRegStatus} =
+ runExceptT (getGroupInfo db cxt user gId) >>= \case
Left e -> False <$ putStrLn ("Error: loading group " <> show gId <> " (skipping): " <> show e)
Right GroupInfo {localDisplayName} -> do
let groupRef = show gId <> " " <> T.unpack localDisplayName
- runExceptT (getHostMember db supportedChatVRange user gId) >>= \case
+ runExceptT (getHostMember db cxt user gId) >>= \case
Left e -> False <$ putStrLn ("Error: loading host member of group " <> groupRef <> " (skipping): " <> show e)
Right GroupMember {groupMemberId = mId', memberContactId = ctId'} -> case dbOwnerMemberId of
Nothing -> True <$ putStrLn ("Warning: group " <> groupRef <> " has no owner member ID, host member ID is " <> show mId' <> ", registration status: " <> B.unpack (strEncode groupRegStatus))
diff --git a/apps/simplex-directory-service/src/Directory/Util.hs b/apps/simplex-directory-service/src/Directory/Util.hs
index a4b79a1bef..52d376a945 100644
--- a/apps/simplex-directory-service/src/Directory/Util.hs
+++ b/apps/simplex-directory-service/src/Directory/Util.hs
@@ -15,9 +15,9 @@ import Simplex.Messaging.Agent.Store.Common (withTransaction)
import qualified Simplex.Messaging.Agent.Store.DB as DB
import Simplex.Messaging.Util (catchAll)
-vr :: ChatController -> VersionRangeChat
-vr ChatController {config = ChatConfig {chatVRange}} = chatVRange
-{-# INLINE vr #-}
+storeCxt :: ChatController -> StoreCxt
+storeCxt ChatController {config} = mkStoreCxt config
+{-# INLINE storeCxt #-}
withDB' :: Text -> ChatController -> (DB.Connection -> IO a) -> IO (Either String a)
withDB' cxt cc a = withDB cxt cc $ ExceptT . fmap Right . a
diff --git a/packages/simplex-chat-nodejs/src/download-libs.js b/packages/simplex-chat-nodejs/src/download-libs.js
index db042d48a2..72761a1ac5 100644
--- a/packages/simplex-chat-nodejs/src/download-libs.js
+++ b/packages/simplex-chat-nodejs/src/download-libs.js
@@ -4,7 +4,7 @@ const path = require('path');
const extract = require('extract-zip');
const GITHUB_REPO = 'simplex-chat/simplex-chat-libs';
-const RELEASE_TAG = 'v6.5.2';
+const RELEASE_TAG = 'v6.5.4';
const BACKEND = (process.env.SIMPLEX_BACKEND || process.env.npm_config_simplex_backend || 'sqlite').toLowerCase();
if (BACKEND !== 'sqlite' && BACKEND !== 'postgres') {
diff --git a/plans/2026-05-20-fix-hold-on-long-msg-android.md b/plans/2026-05-20-fix-hold-on-long-msg-android.md
new file mode 100644
index 0000000000..f89aa26582
--- /dev/null
+++ b/plans/2026-05-20-fix-hold-on-long-msg-android.md
@@ -0,0 +1,68 @@
+# Fix chat item long-press menu and ripple shape
+
+Branch: `nd/fix-hold-on-long-msg-android` · PR [#6997](https://github.com/simplex-chat/simplex-chat/pull/6997) · issue [#6991](https://github.com/simplex-chat/simplex-chat/issues/6991).
+
+## 1. Problem statement
+
+Two issues with the chat-item bubble on the multiplatform UI:
+
+- **Android (#6991):** long-pressing the lower part of a very tall text message did not open the select/copy/reply context menu. Long-press on the top/middle worked. Reproduced with a long multi-line message (~150+ lines — e.g. 5000 random bytes as hex); never reproduced on short messages. Occurs **only with the message tail enabled** (bubble shape); with the tail preference disabled, messages use a plain rounded-rectangle shape and the bug does not reproduce. iOS unaffected.
+- **Desktop:** the chat-item press ripple, in some cases, rendered as a rectangle instead of following the rounded bubble shape.
+
+## 2. Solution summary
+
+One function — `Modifier.clipChatItem` in `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt`. It clipped the chat item with `Modifier.clip(shape)` for every shape style. It now clips the **bubble** (`GenericShape`) in the draw pass with `drawWithCache` + `clipPath`, and keeps `Modifier.clip` for the **`RoundRect`** shape, which is unaffected by the bug (§3).
+
+```kotlin
+return when (style) {
+ is ShapeStyle.Bubble -> {
+ val shape = chatItemShape(cornerRoundness, LocalDensity.current, style.tailVisible, chatItem?.chatDir?.sent == true)
+ this.drawWithCache {
+ val path = Path().apply { addOutline(shape.createOutline(size, layoutDirection, this@drawWithCache)) }
+ onDrawWithContent { clipPath(path) { this@onDrawWithContent.drawContent() } }
+ }
+ }
+ is ShapeStyle.RoundRect -> this.clip(RoundedCornerShape(style.radius * cornerRoundness))
+}
+```
+
+Net diff: 1 file (`ChatItemView.kt`), +20 / −5 — the `clipChatItem` function restructured plus two imports.
+
+## 3. Root cause
+
+`Modifier.clip(shape)` is defined in Compose as `graphicsLayer(shape = shape, clip = true)`. A clipping graphics layer restricts **both** drawing **and** pointer hit-test to the shape.
+
+`clipChatItem` is the first (outermost) modifier on the chat-bubble `Column`, and that same `Column` carries the `combinedClickable` long-press handler. So the layer's hit-test region gates every press on the bubble.
+
+- **Android:** for a very tall chat item the layer's hit-test region does not cover the bubble's lower portion — a press there is never delivered to `combinedClickable`, so the long-press menu does not open. This is specific to the bubble's `GenericShape` clip: with the tail disabled the item is clipped with a `RoundedCornerShape`, which hit-tests correctly. The exact reason the `GenericShape` clip's hit-test falls short on tall content was not isolated; the fix does not depend on it (see §4).
+- **Desktop:** the layer's clip did not always extend to the `combinedClickable` press ripple, so the ripple drew to its own rectangular bounds instead of the bubble shape.
+
+## 4. The fix
+
+For the bubble shape, `clipChatItem` clips with a draw modifier instead of a graphics layer. `drawWithCache` builds the shape's `Path` once per size change; `onDrawWithContent { clipPath(path) { drawContent() } }` wraps the whole content draw — bubble background, text, and the press ripple — in a canvas clip.
+
+A draw modifier affects **only drawing**. It is not a layout or pointer-input node and has no effect on hit-test. Therefore:
+
+- the bubble and ripple are still clipped to the shape — visually identical to `Modifier.clip`;
+- pointer hit-test is no longer clipped — `combinedClickable` receives presses anywhere in the `Column`'s bounds, fixing the Android long-press;
+- the canvas `clipPath` clips the ripple reliably, fixing the rectangular desktop ripple.
+
+The `RoundRect` shape keeps `Modifier.clip`: it hit-tests correctly (no bug) and keeps its antialiased outline clip. Scoping by shape — rather than draw-clipping every shape — leaves every non-bubble chat item (service/event messages, tails-off messages, old Android) byte-for-byte unchanged.
+
+## 5. Alternatives rejected
+
+- **Remove `clipChatItem` from the bubble `Column`.** Fixes the Android long-press, but the press ripple loses its shape and renders as a rectangle. Intermediate state during development; replaced.
+- **Draw-pass clip for every shape, unconditionally.** Also correct and a hair simpler (no `when`), but it needlessly moves the `RoundRect` shape off `Modifier.clip`'s antialiased outline clip onto a canvas `clipPath` — a behaviour change with no benefit, since `RoundRect` has no bug. Scoping to the bubble shape keeps `RoundRect` unchanged.
+- **Keep `Modifier.clip`, move `combinedClickable` off the clipped `Column`.** A larger structural change to the chat-item layout tree; the draw-pass clip fixes both issues without moving anything.
+
+## 6. Verification
+
+- **Android** (debug APK): long-press on the lower half of a 150+-line message opens the context menu; top/middle still work; the tap ripple stays bubble-shaped; swipe-to-reply and link tap/long-press are unaffected.
+- **Desktop** (Linux AppImage): the chat-item press ripple follows the bubble shape (rounded corners and tail), not a rectangle — confirmed against a build without the fix.
+- The bubble draw-pass clip above was verified on those Android and desktop builds; this revision additionally keeps `Modifier.clip` for the `RoundRect` shape, which is the unchanged pre-fix behaviour.
+
+## 7. Risk and rollback
+
+- Blast radius: the `Bubble` branch of `clipChatItem`. The `RoundRect` branch is unchanged (`Modifier.clip` as before), so service/event items, tails-off messages and old-Android items are untouched. For the bubble, drawing is clipped identically; the single behavioural change is that pointer hit-test on the bubble is no longer shape-clipped — benign (bubble corners are transparent; a rectangular hit area is a marginally larger touch target).
+- iOS is a separate codebase and is untouched.
+- Rollback: revert the fix commit on the branch, or drop it before merge.
diff --git a/plans/2026-05-26-fix-video-drag-and-drop.md b/plans/2026-05-26-fix-video-drag-and-drop.md
new file mode 100644
index 0000000000..16258dea7f
--- /dev/null
+++ b/plans/2026-05-26-fix-video-drag-and-drop.md
@@ -0,0 +1,44 @@
+# Fix desktop drag-and-drop of videos attached as files
+
+Branch: `nd/fix-video-drag-and-drop` · base: `master`.
+
+## Problem
+
+On desktop, dragging a video file into a chat attaches it as a generic file (paperclip + filename) instead of as a video (thumbnail + duration). Dragging an image works. Picking the same video via "Gallery → Video" attaches it correctly — so only the drag-and-drop routing is wrong.
+
+## Fix
+
+One file: `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt`. Recognise videos as media in `onFilesAttached`'s classifier.
+
+```diff
+ fun MutableState.onFilesAttached(uris: List) {
+- val groups = uris.groupBy { isImage(it) }
+- val images = groups[true] ?: emptyList()
++ val groups = uris.groupBy { isImage(it) || isVideoUri(it) }
++ val media = groups[true] ?: emptyList()
+ val files = groups[false] ?: emptyList()
+- if (images.isNotEmpty()) {
+- CoroutineScope(Dispatchers.IO).launch { processPickedMedia(images, null) }
++ if (media.isNotEmpty()) {
++ CoroutineScope(Dispatchers.IO).launch { processPickedMedia(media, null) }
+ } else if (files.isNotEmpty()) {
+ processPickedFile(uris.first(), null)
+ }
+ }
++
++private fun isVideoUri(uri: URI): Boolean {
++ val name = getFileName(uri)?.lowercase() ?: return false
++ return name.endsWith(".mov") || name.endsWith(".avi") || name.endsWith(".mp4") ||
++ name.endsWith(".mpg") || name.endsWith(".mpeg") || name.endsWith(".mkv")
++}
+```
+
+Total diff: 1 file, +11 / −5.
+
+## Cause
+
+`onFilesAttached` classified URIs by `isImage` only — non-images (including videos) fell through to `processPickedFile`, producing a `FilePreview`. The downstream `processPickedMedia` already handles video correctly (its `else` branch builds `UploadContent.Video`); the classifier above it just never reached that branch. The existing `isVideo` in `Videos.desktop.kt` is `desktopMain`-only and not visible from `ComposeView.kt` in `commonMain` — the structural gap that left the classifier video-blind. The inline `isVideoUri` uses the cross-platform `getFileName`, so the same fix also corrects the paste path (`onFilesPasted` at `ComposeView.kt:1378`).
+
+## Risk
+
+One file, no interface change. Image and non-media drops are bit-identical. Video extension list is now duplicated with `Videos.desktop.kt`; adding a new format means updating both — accepted as the cost of a single-file fix. iOS unaffected. Rollback: revert the commit.
diff --git a/plans/delete-leave-dialog-with-profile-impl.md b/plans/delete-leave-dialog-with-profile-impl.md
new file mode 100644
index 0000000000..860d555d36
--- /dev/null
+++ b/plans/delete-leave-dialog-with-profile-impl.md
@@ -0,0 +1,323 @@
+# Implementation plan — chat name on its own line in delete/leave/clear dialogs
+
+Follows the product spec in
+[`delete-leave-dialog-with-profile.md`](./delete-leave-dialog-with-profile.md).
+
+Pure code change — zero string additions, zero new helpers, zero
+signature changes. Each call site edits one argument: the `text =` /
+`message:` value gains `"${displayName}\n\n"` prepended to the
+existing localized warning (or, where there is no current body, the
+chat name becomes the new body).
+
+One commit per platform.
+
+## Commit 1 — Kotlin
+
+**Files touched:**
+- `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt`
+ — adds `parseHtml: Boolean = true` to `showAlertDialog` and
+ `showAlertDialogButtonsColumn`. When `false`, the body text is wrapped
+ as `AnnotatedString` and routed through the existing AnnotatedString
+ `AlertContent` overload, which does NOT call
+ `escapedHtmlToAnnotatedString`. Default stays `true` so existing
+ callers are unaffected.
+- `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt`
+- `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt`
+- `apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt`
+ — adds the previously-missed `deleteContactConnectionAlert`
+ dispatcher to the coverage (pending contact connections).
+
+Every Kotlin call site that prepends the chat name sets
+`parseHtml = false`, so `displayName` is never HTML-interpreted.
+
+### 1.1 — `deleteGroupDialog` (`GroupChatInfoView.kt:182`)
+
+```diff
+ fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
+ val chatInfo = chat.chatInfo
+ val titleId = /* unchanged */
+ val messageId = /* unchanged */
+ AlertManager.shared.showAlertDialog(
+ title = generalGetString(titleId),
+- text = generalGetString(messageId),
++ text = "${groupInfo.displayName}\n\n${generalGetString(messageId)}",
+ confirmText = generalGetString(MR.strings.delete_verb),
+ onConfirm = { /* unchanged */ },
+ destructive = true,
+ )
+ }
+```
+
+### 1.2 — `leaveGroupDialog` (`GroupChatInfoView.kt:222`)
+
+```diff
+ fun leaveGroupDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
+ val titleId = /* unchanged */
+ val messageId = /* unchanged */
+ AlertManager.shared.showAlertDialog(
+ title = generalGetString(titleId),
+- text = generalGetString(messageId),
++ text = "${groupInfo.displayName}\n\n${generalGetString(messageId)}",
+ confirmText = generalGetString(MR.strings.leave_group_button),
+ onConfirm = { /* unchanged */ },
+ destructive = true,
+ )
+ }
+```
+
+Signature unchanged. No caller updates. `groupInfo.displayName` is
+already available on the existing parameter (`ChatModel.kt:2142`).
+
+### 1.3 — `clearChatDialog` (`ChatInfoView.kt:492`)
+
+```diff
+ fun clearChatDialog(chat: Chat, close: (() -> Unit)? = null) {
+ AlertManager.shared.showAlertDialog(
+ title = generalGetString(MR.strings.clear_chat_question),
+- text = generalGetString(MR.strings.clear_chat_warning),
++ text = "${chat.chatInfo.displayName}\n\n${generalGetString(MR.strings.clear_chat_warning)}",
+ confirmText = generalGetString(MR.strings.clear_verb),
+ onConfirm = { controller.clearChat(chat, close) },
+ destructive = true,
+ )
+ }
+```
+
+### 1.4 — Contact-delete dispatchers (`ChatInfoView.kt`)
+
+Four functions. `deleteContactOrConversationDialog` (line 248) has
+no existing `text =`, so the chat name becomes the new body. The
+other three already have a `text =`, so the name is prepended.
+
+All four already have `contact: Contact` as a parameter, so
+`contact.displayName` is used directly (same value as
+`chat.chatInfo.displayName` for a direct chat, shorter expression).
+
+```diff
+ // deleteContactOrConversationDialog — line 248
+ private fun deleteContactOrConversationDialog(chat: Chat, contact: Contact, chatModel: ChatModel, close: (() -> Unit)?) {
+ AlertManager.shared.showAlertDialogButtonsColumn(
+ title = generalGetString(MR.strings.delete_contact_question),
++ text = contact.displayName,
+ buttons = { /* unchanged */ }
+ )
+ }
+```
+
+```diff
+ // deleteActiveContactDialog — line 304
+ private fun deleteActiveContactDialog(chat: Chat, contact: Contact, chatModel: ChatModel, close: (() -> Unit)? = null) {
+ val contactDeleteMode = mutableStateOf(ContactDeleteMode.Full())
+ AlertManager.shared.showAlertDialogButtonsColumn(
+ title = generalGetString(MR.strings.delete_contact_question),
+- text = generalGetString(MR.strings.delete_contact_cannot_undo_warning),
++ text = "${contact.displayName}\n\n${generalGetString(MR.strings.delete_contact_cannot_undo_warning)}",
+ buttons = { /* unchanged */ }
+ )
+ }
+```
+
+Same diff for `deleteContactWithoutConversation` (line 361) and
+`deleteNotReadyContact` (line 417) — both use
+`delete_contact_cannot_undo_warning`. Neither takes `contact` as
+a parameter, so the name is read via `chat.chatInfo.displayName`
+(which resolves to `contact.displayName` because these dispatchers
+are only reached for `ChatInfo.Direct` chats). Their titles
+(`confirm_delete_contact_question`) stay unchanged — the
+not-ready / no-conversation paths keep their distinct title.
+
+## Commit 2 — iOS
+
+**Files touched:**
+- `apps/ios/Shared/Views/ChatList/ChatListNavLink.swift`
+- `apps/ios/Shared/Views/Chat/ChatInfoView.swift`
+- `apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift`
+
+### 2.1 — `deleteGroupAlert` (two locations)
+
+`Views/Chat/Group/GroupChatInfoView.swift:835` and
+`Views/ChatList/ChatListNavLink.swift:567` get the same diff.
+`deleteGroupAlertMessage(_:)` already returns a `Text` containing
+the localized warning — concatenate to it.
+
+```diff
+ private func deleteGroupAlert() -> Alert {
+ let label: LocalizedStringKey = /* unchanged */
+ return Alert(
+ title: Text(label),
+- message: deleteGroupAlertMessage(groupInfo),
++ message: Text(chat.chatInfo.displayName) + Text(verbatim: "\n\n") + deleteGroupAlertMessage(groupInfo),
+ primaryButton: .destructive(Text("Delete")) { /* unchanged */ },
+ secondaryButton: .cancel()
+ )
+ }
+```
+
+`Text(chat.chatInfo.displayName)` resolves to `Text(_ content: some StringProtocol)`
+(the runtime-string overload — no localization lookup, matches
+codebase convention: `ChatView.swift:984`, `ChatInfoToolbar.swift:49`,
+`SettingsView.swift:540`). `Text(verbatim: "\n\n")` is the literal
+separator, matching the codebase convention that reserves
+`verbatim:` for fixed punctuation (`ContextItemView.swift:88` is
+the textbook example: `Text(chatLink.displayName) + Text(verbatim: " - ")`).
+The third term `Text(messageLabel)` keeps the existing
+`LocalizedStringKey` lookup.
+
+### 2.2 — `leaveGroupAlert` (two locations)
+
+`Views/Chat/Group/GroupChatInfoView.swift:872` and
+`Views/ChatList/ChatListNavLink.swift:622`:
+
+```diff
+ private func leaveGroupAlert() -> Alert {
+ let titleLabel: LocalizedStringKey = /* unchanged */
+ let messageLabel: LocalizedStringKey = /* unchanged */
+ return Alert(
+ title: Text(titleLabel),
+- message: Text(messageLabel),
++ message: Text(chat.chatInfo.displayName) + Text(verbatim: "\n\n") + Text(messageLabel),
+ primaryButton: .destructive(Text("Leave")) { /* unchanged */ },
+ secondaryButton: .cancel()
+ )
+ }
+```
+
+### 2.3 — `clearChatAlert` (three locations)
+
+`Views/Chat/ChatInfoView.swift:577`,
+`Views/Chat/Group/GroupChatInfoView.swift:858`,
+`Views/ChatList/ChatListNavLink.swift:600`:
+
+```diff
+ private func clearChatAlert() -> Alert {
+ Alert(
+ title: Text("Clear conversation?"),
+- message: Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
++ message: Text(chat.chatInfo.displayName) + Text(verbatim: "\n\n") + Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
+ primaryButton: .destructive(Text("Clear")) { /* unchanged */ },
+ secondaryButton: .cancel()
+ )
+ }
+```
+
+### 2.4 — Contact-delete action sheets
+
+Three functions in `Views/Chat/ChatInfoView.swift`. None currently
+pass `message:` to `ActionSheet`; we add it. `ActionSheet`'s
+`message:` is an optional second parameter that SwiftUI already
+supports.
+
+All three functions have `contact: Contact` in scope. Use bare
+`Text(contact.displayName)` (resolves to the `StringProtocol`
+overload, no localization lookup, matches codebase convention).
+Add only the name as `message:` — these ActionSheets had no
+message before, so adding any additional warning would be new
+behavior beyond the stated goal.
+
+**`deleteContactOrConversationDialog`** (line 1177):
+
+```diff
+ private func deleteContactOrConversationDialog(
+ _ chat: Chat, _ contact: Contact, _ dismissToChatList: Bool,
+ _ showAlert: @escaping (SomeAlert) -> Void,
+ _ showActionSheet: @escaping (SomeActionSheet) -> Void,
+ _ showSheetContent: @escaping (SomeSheet) -> Void
+ ) {
+ showActionSheet(SomeActionSheet(
+ actionSheet: ActionSheet(
+ title: Text("Delete contact?"),
++ message: Text(contact.displayName),
+ buttons: [ /* unchanged */ ]
+ ),
+ id: "deleteContactOrConversationDialog"
+ ))
+ }
+```
+
+**`deleteContactWithoutConversation`** (line 1324):
+
+```diff
+ showActionSheet(SomeActionSheet(
+ actionSheet: ActionSheet(
+ title: Text("Confirm contact deletion?"),
++ message: Text(contact.displayName),
+ buttons: [ /* unchanged */ ]
+ ),
+ id: "deleteContactWithoutConversation"
+ ))
+```
+
+**`deleteNotReadyContact`** (line 1348) — same:
+
+```diff
+ showActionSheet(SomeActionSheet(
+ actionSheet: ActionSheet(
+ title: Text("Confirm contact deletion?"),
++ message: Text(contact.displayName),
+ buttons: [ /* unchanged */ ]
+ ),
+ id: "deleteNotReadyContact"
+ ))
+```
+
+### 2.5 — `DeleteActiveContactDialog` sheet (line 1282) unchanged
+
+The secondary multi-option sheet is reached only after the user
+confirms "Delete contact" in the previous action sheet — which now
+shows the name. The sheet itself remains as-is.
+
+## Verification
+
+For each platform, exercise every entry point and confirm the
+body reads `` on its own line followed by the existing
+warning:
+
+- Android & Desktop:
+ - Chat list swipe — direct contact, group, channel, business chat
+ → delete / clear / leave. (Note folder's clear dialog is
+ intentionally unchanged — `clearNoteFolderDialog` excluded.)
+ - Chat info screens — "Delete contact" / "Delete group" / "Delete
+ channel" / "Clear conversation" / "Leave …" rows.
+ - Contact list (`ContactListNavView.kt:148`) — "Delete contact"
+ action shows the name in entry-point dialog and toggle dialog.
+ - Multi-option contact-delete path: entry dialog (now has a name
+ body where it had none) → toggle dialog (name above the
+ warning) → success.
+- iOS:
+ - Same matrix from chat list swipe and chat info screens.
+ - Action-sheet contact-delete paths show the name as the
+ `message:` line on iPhone and iPad.
+
+Edge cases:
+
+- Long chat name — alert containers wrap automatically; the body
+ occupies 3+ lines. Confirm with a chat renamed to ~40 characters.
+- Special characters (emoji, RTL, double quotes) — render literally
+ via string interpolation, no format-substitution involved.
+- Empty `displayName` — does not occur in practice (`NamedChat`
+ enforces non-empty via `localAlias.ifEmpty { profile.displayName }`).
+
+Diff-level checks:
+
+- `git diff '*strings.xml' '*Localizable.strings'` returns zero
+ hunks. Pure code change.
+- `git diff --stat` shows ~5 files total: two Kotlin dispatcher
+ files, three iOS view files.
+- Cancel/confirm flows behave exactly as before — same API calls,
+ same model updates, same navigation.
+
+## Out of scope
+
+- Profile picture / avatar in dialogs — excluded by product decision.
+- Refactoring the iOS duplication between `ChatListNavLink` and
+ `GroupChatInfoView` / `ChatInfoView` (pre-existing `// TODO` at
+ `GroupChatInfoView.swift:834`).
+- Pre-existing wording divergence between Kotlin's "Clear chat?"
+ and iOS's "Clear conversation?". Both platforms keep their
+ titles.
+- "Delete invitation" at `ChatListNavLink.swift:236` — has no
+ confirmation dialog (direct call to `deleteChat(chat)`); nothing
+ to modify.
+- Bolding the chat name. SwiftUI `Text + Text` supports `.bold()`
+ on the first term, but Jetpack Compose `AlertDialog` text is a
+ single unstyled string — keeping both unstyled preserves parity.
diff --git a/plans/delete-leave-dialog-with-profile.md b/plans/delete-leave-dialog-with-profile.md
new file mode 100644
index 0000000000..a05fb66532
--- /dev/null
+++ b/plans/delete-leave-dialog-with-profile.md
@@ -0,0 +1,249 @@
+# Show chat name in delete / leave / clear confirmation dialogs
+
+## Goal
+
+The current delete-contact, delete-group, delete-channel, leave-group,
+leave-channel and clear-chat confirmations are generic. From a long
+chat list, swiping on a row and triggering one of these actions opens
+a dialog whose title is "Delete group?", "Leave channel?", "Clear
+conversation?" — with no indication of *which* chat is the target. A
+user can easily act on the wrong chat.
+
+The fix: include the chat's display name in the dialog body, on a line
+of its own above the existing warning text. Nothing else changes —
+same title, same warning text, same buttons, same colors, same dialog
+shape. No profile picture, no layout changes, no new helpers, no new
+translation strings.
+
+We deliberately do NOT reuse the open-chat-link alert layout (centered
+profile image + name + open-chat button). That layout is the *invite*
+flow's identity; repurposing it for destructive confirmations would
+confuse the two flows visually. The minimum change that solves the
+"which chat?" problem is putting the name in the body text.
+
+## Why body, not title; why no new strings
+
+The title carries the action ("Delete group?", "Leave channel?"). The
+body carries the consequences ("Group will be deleted for all
+members…"). The chat name belongs with the body — it is the subject
+of the consequence, not part of the question.
+
+Adding the name to the title would require new format-string variants
+(`delete_group_named_question` etc.) and per-locale re-translation.
+Putting the name on its own line in the body is a pure code change —
+the existing translated warnings are concatenated with the chat name
+in code:
+
+```
+Tech Talk
+
+Group will be deleted for all members - this cannot be undone!
+```
+
+The display name appears first because the user wants to confirm
+*which* chat before reading *what* will happen. The blank line between
+the name and the warning makes the name visually distinct.
+
+## Current state
+
+### Multiplatform (Kotlin / Android / Desktop)
+
+All eight dialogs go through `AlertManager.shared.showAlertDialog` or
+`showAlertDialogButtonsColumn`:
+
+- `deleteGroupDialog` — `views/chat/group/GroupChatInfoView.kt:182`
+- `leaveGroupDialog` — `views/chat/group/GroupChatInfoView.kt:222`
+- `clearChatDialog` — `views/chat/ChatInfoView.kt:492`
+- `deleteContactOrConversationDialog` — `views/chat/ChatInfoView.kt:248`
+- `deleteActiveContactDialog` — `views/chat/ChatInfoView.kt:304`
+- `deleteContactWithoutConversation` — `views/chat/ChatInfoView.kt:361`
+- `deleteNotReadyContact` — `views/chat/ChatInfoView.kt:417`
+- `deleteContactConnectionAlert` — `views/chatlist/ChatListNavLinkView.kt:772`
+ (deletes a pending contact connection; takes a `PendingContactConnection`
+ whose `displayName` reflects any custom name the user set)
+
+Call sites (chat-info screens, chat-list swipe / overflow, contact
+list) funnel through these dispatcher functions.
+
+### iOS (Swift)
+
+Two SwiftUI patterns are used:
+
+- SwiftUI `Alert` with `primaryButton: .destructive` / `.cancel()`:
+ - `deleteGroupAlert` — `Views/ChatList/ChatListNavLink.swift:567`,
+ `Views/Chat/Group/GroupChatInfoView.swift:835`
+ - `leaveGroupAlert` — `Views/ChatList/ChatListNavLink.swift:622`,
+ `Views/Chat/Group/GroupChatInfoView.swift:872`
+ - `clearChatAlert` — `Views/ChatList/ChatListNavLink.swift:600`,
+ `Views/Chat/ChatInfoView.swift:577`,
+ `Views/Chat/Group/GroupChatInfoView.swift:858`
+- SwiftUI `ActionSheet`:
+ - `deleteContactOrConversationDialog` —
+ `Views/Chat/ChatInfoView.swift:1177`
+ - `deleteContactWithoutConversation` —
+ `Views/Chat/ChatInfoView.swift:1324`
+ - `deleteNotReadyContact` — `Views/Chat/ChatInfoView.swift:1348`
+
+`Alert(message:)` accepts `Text`, and `ActionSheet(message:)` (an
+existing optional parameter not used today) accepts `Text` too — so
+the name can be added by composing the existing message string with
+`"\n\n"` and the chat name. No widget changes.
+
+## Design
+
+| Dialog | Body today | Body after |
+|---|---|---|
+| Delete group | `Group will be deleted for all members – this cannot be undone!` | `Tech Talk` + blank line + existing text |
+| Delete channel | `Channel will be deleted for all subscribers – this cannot be undone!` | `SimpleX news` + blank line + existing text |
+| Leave group | `You will stop receiving messages from this group. …` | `Tech Talk` + blank line + existing text |
+| Clear chat | `All messages will be deleted – this cannot be undone! …` | `Alice` + blank line + existing text |
+| Delete contact (entry sheet) | *(no body today — title only + buttons)* | `Alice` (becomes the body) |
+| Delete contact (active variant) | `Contact will be deleted – this cannot be undone!` | `Alice` + blank line + existing text |
+| Confirm contact deletion (not-ready / no-conversation) | `Contact will be deleted – this cannot be undone!` | `Alice` + blank line + existing text |
+
+Title text is unchanged in every case. Existing titles
+(`delete_contact_question`, `confirm_delete_contact_question`, etc.)
+keep their semantic distinction — the "Confirm contact deletion?"
+title still appears for the not-ready / no-conversation paths.
+
+### Which name to use: `displayName`, not `chatViewName`
+
+The chat list row labels chats with `cInfo.chatViewName`
+(`ChatPreviewView.kt:87`), defined as:
+
+```kotlin
+val chatViewName: String
+ get() = localAlias.ifEmpty { displayName + (if (fullName == "" || fullName == displayName) "" else " / $fullName") }
+```
+
+The dialog uses `chatInfo.displayName` (and `groupInfo.displayName`
+for the leave dialog). For most chats these are identical:
+
+- If `localAlias` is set, both resolve to the alias.
+- If `displayName == fullName` (or `fullName` is empty), both resolve
+ to `displayName`.
+
+For a contact with distinct display name and full name (no alias),
+the row would show `alice / Alice Smith` while the dialog shows
+`alice`. Acceptable: `displayName` is the recognizable identifier,
+shorter, and the dialog format (single line above the warning)
+benefits from concision. Two-part identifiers in the dialog would
+crowd the layout.
+
+### `clearNoteFolderDialog` is excluded
+
+The local notes folder is a single-instance object — there is only
+one per user — and its existing warning text already names it
+unambiguously. Adding the display name on its own line would be
+pure redundancy. Skipped.
+
+## Changes
+
+### Multiplatform (Kotlin)
+
+Each dispatcher function changes one argument: the `text =` parameter
+passed to `AlertManager.shared.showAlertDialog` /
+`showAlertDialogButtonsColumn`. The new value is the chat name + two
+newlines + the existing message text:
+
+```kotlin
+text = "${chatInfo.displayName}\n\n${generalGetString(messageId)}",
+parseHtml = false,
+```
+
+`parseHtml = false` is a new boolean parameter added to both alert
+helpers. It bypasses `escapedHtmlToAnnotatedString` so the
+user-controlled `displayName` is rendered as literal text, never
+interpreted as HTML markup (``, ``, `&`, etc.). The default
+remains `true`; only our delete-confirmation dispatchers opt out.
+
+For `leaveGroupDialog` the source is `groupInfo.displayName` (the
+function already takes `groupInfo` — no signature change needed,
+no caller updates needed).
+
+For `deleteGroupDialog`, also `groupInfo.displayName`, for consistency
+with `leaveGroupDialog` (both have `groupInfo` already in scope).
+
+For `deleteContactOrConversationDialog`, which has no `text =`
+parameter today, add `text = chatInfo.displayName` (no concatenation
+needed — the dialog had no body text before).
+
+### iOS
+
+Each of the eight call sites changes one argument: the `message:`
+parameter passed to `Alert(…)` or `ActionSheet(…)`. The new value
+composes the chat name with the existing localized message string:
+
+```swift
+message: Text("\(chat.chatInfo.displayName)\n\n\(existingMessage)"),
+```
+
+For the three `ActionSheet` sites that have no `message:` today, add
+`message: Text(chat.chatInfo.displayName)`.
+
+## Out of scope
+
+- Profile picture / avatar in any of these dialogs — excluded by
+ decision: the open-chat-link alert owns that layout, and reusing
+ it for destructive confirmations conflates two semantically
+ different flows.
+- The pre-existing wording divergence between Kotlin's
+ `clear_chat_question` ("Clear chat?") and iOS's "Clear
+ conversation?". Both platforms keep their existing titles.
+- Refactoring the iOS duplication between `ChatListNavLink` and
+ `GroupChatInfoView` / `ChatInfoView` (pre-existing `// TODO reuse
+ this and clearChatAlert with ChatInfoView` at
+ `GroupChatInfoView.swift:834`).
+- "Delete invitation" at `ChatListNavLink.swift:236` — goes through
+ `deleteChat(chat)` directly with no confirmation dialog. No dialog
+ to modify.
+- Bolding the chat name on its own line. SwiftUI `Text` concatenation
+ supports `.bold()`; Jetpack Compose `AlertDialog` text is a single
+ string. Keep both platforms unstyled for parity.
+
+## Verification
+
+Per platform, exercise every entry point and confirm the dialog body
+reads `` on its own line followed by a blank line followed
+by the existing warning:
+
+- Android & Desktop:
+ - Chat list swipe — direct contact, group, channel, business chat
+ → delete / clear / leave actions. (Note folder's clear dialog
+ is intentionally unchanged.)
+ - Chat info screens — "Delete contact" / "Delete group" / "Delete
+ channel" / "Clear conversation" / "Leave …" rows.
+ - Contact list (`ContactListNavView.kt:148`) — "Delete contact"
+ action.
+ - The multi-option contact-delete path: entry dialog (now has a
+ name body where it had none) → toggle dialog (name above the
+ warning) → success.
+- iOS:
+ - Same matrix from chat list swipe and chat info screens.
+ - Action-sheet contact-delete paths show the name as the
+ `message:` line.
+
+Edge cases:
+
+- Long chat name (40+ chars) — alert containers wrap automatically;
+ body now occupies 3+ lines (name on 2, blank line, warning on 1+).
+ Confirm via a chat renamed to a long string.
+- Special characters in name (emoji, RTL text, double quotes) —
+ render literally because the substitution is string concatenation,
+ not format expansion. A contact named `Bob "the builder"` displays
+ as `Bob "the builder"` on its own line. No quoting/escaping issue.
+- Empty `displayName` would render an empty first line above the
+ warning. In practice `displayName` is non-empty (the `NamedChat`
+ interface enforces it via `localAlias.ifEmpty { profile.displayName }`);
+ no defensive trimming added.
+
+Diff-level checks:
+
+- `git diff strings.xml` and `git diff '*Localizable.strings'` show
+ zero hunks. The change is pure code.
+- `git diff --stat` shows each platform touched in 2–4 files:
+ the dispatcher file(s) on Kotlin (`ChatInfoView.kt`,
+ `GroupChatInfoView.kt`), and the SwiftUI views holding the
+ alert/sheet builders on iOS.
+- Behavior is unchanged. Cancel returns to the prior screen;
+ confirm performs the same destructive API call as before.
diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs
index 067fdc5ea9..657d20cb3f 100644
--- a/src/Simplex/Chat/Controller.hs
+++ b/src/Simplex/Chat/Controller.hs
@@ -169,6 +169,12 @@ data ChatConfig = ChatConfig
chatHooks :: ChatHooks
}
+-- | Builds the read-only context threaded through store functions from chat config.
+-- The single construction point, so new store-wide config (e.g. server keys) is added in one place.
+mkStoreCxt :: ChatConfig -> StoreCxt
+mkStoreCxt ChatConfig {chatVRange} = StoreCxt chatVRange
+{-# INLINE mkStoreCxt #-}
+
data RandomAgentServers = RandomAgentServers
{ smpServers :: NonEmpty (ServerCfg 'PSMP),
xftpServers :: NonEmpty (ServerCfg 'PXFTP)
diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs
index 2e48d7615c..fb7524decb 100644
--- a/src/Simplex/Chat/Library/Commands.hs
+++ b/src/Simplex/Chat/Library/Commands.hs
@@ -328,8 +328,8 @@ execChatCommand rh s retryNum =
execChatCommand' :: ChatCommand -> Int -> CM' (Either ChatError ChatResponse)
execChatCommand' cmd retryNum = handleCommandError $ do
- vr <- chatVersionRange
- processChatCommand vr (NRMInteractive' retryNum) cmd
+ cxt <- chatStoreCxt
+ processChatCommand cxt (NRMInteractive' retryNum) cmd
execRemoteCommand :: RemoteHostId -> ChatCommand -> ByteString -> Int -> CM' (Either ChatError ChatResponse)
execRemoteCommand rhId cmd s retryNum = handleCommandError $ getRemoteHostClient rhId >>= \rh -> processRemoteCommand rhId rh cmd s retryNum
@@ -346,8 +346,8 @@ parseChatCommand :: ByteString -> Either String ChatCommand
parseChatCommand = A.parseOnly chatCommandP . B.dropWhileEnd isSpace
-- | Chat API commands interpreted in context of a local zone
-processChatCommand :: VersionRangeChat -> NetworkRequestMode -> ChatCommand -> CM ChatResponse
-processChatCommand vr nm = \case
+processChatCommand :: StoreCxt -> NetworkRequestMode -> ChatCommand -> CM ChatResponse
+processChatCommand cxt nm = \case
ShowActiveUser -> withUser' $ pure . CRActiveUser
CreateActiveUser NewUser {profile, pastTimestamp, userChatRelay, clientService} -> do
forM_ profile $ \Profile {displayName} -> checkValidName displayName
@@ -413,26 +413,26 @@ processChatCommand vr nm = \case
SetActiveUser uName viewPwd_ -> do
tryAllErrors (withFastStore (`getUserIdByName` uName)) >>= \case
Left _ -> throwChatError CEUserUnknown
- Right userId -> processChatCommand vr nm $ APISetActiveUser userId viewPwd_
+ Right userId -> processChatCommand cxt nm $ APISetActiveUser userId viewPwd_
SetAllContactReceipts onOff -> withUser $ \_ -> withFastStore' (`updateAllContactReceipts` onOff) >> ok_
APISetUserContactReceipts userId' settings -> withUser $ \user -> do
user' <- privateGetUser userId'
validateUserPassword user user' Nothing
withFastStore' $ \db -> updateUserContactReceipts db user' settings
ok user
- SetUserContactReceipts settings -> withUser $ \User {userId} -> processChatCommand vr nm $ APISetUserContactReceipts userId settings
+ SetUserContactReceipts settings -> withUser $ \User {userId} -> processChatCommand cxt nm $ APISetUserContactReceipts userId settings
APISetUserGroupReceipts userId' settings -> withUser $ \user -> do
user' <- privateGetUser userId'
validateUserPassword user user' Nothing
withFastStore' $ \db -> updateUserGroupReceipts db user' settings
ok user
- SetUserGroupReceipts settings -> withUser $ \User {userId} -> processChatCommand vr nm $ APISetUserGroupReceipts userId settings
+ SetUserGroupReceipts settings -> withUser $ \User {userId} -> processChatCommand cxt nm $ APISetUserGroupReceipts userId settings
APISetUserAutoAcceptMemberContacts userId' onOff -> withUser $ \user -> do
user' <- privateGetUser userId'
validateUserPassword user user' Nothing
withFastStore' $ \db -> updateUserAutoAcceptMemberContacts db user' onOff
ok user
- SetUserAutoAcceptMemberContacts onOff -> withUser $ \User {userId} -> processChatCommand vr nm $ APISetUserAutoAcceptMemberContacts userId onOff
+ SetUserAutoAcceptMemberContacts onOff -> withUser $ \User {userId} -> processChatCommand cxt nm $ APISetUserAutoAcceptMemberContacts userId onOff
APIHideUser userId' (UserPwd viewPwd) -> withUser $ \user -> do
user' <- privateGetUser userId'
case viewPwdHash user' of
@@ -458,10 +458,10 @@ processChatCommand vr nm = \case
setUserPrivacy user user' {viewPwdHash = Nothing, showNtfs = True}
APIMuteUser userId' -> setUserNotifications userId' False
APIUnmuteUser userId' -> setUserNotifications userId' True
- HideUser viewPwd -> withUser $ \User {userId} -> processChatCommand vr nm $ APIHideUser userId viewPwd
- UnhideUser viewPwd -> withUser $ \User {userId} -> processChatCommand vr nm $ APIUnhideUser userId viewPwd
- MuteUser -> withUser $ \User {userId} -> processChatCommand vr nm $ APIMuteUser userId
- UnmuteUser -> withUser $ \User {userId} -> processChatCommand vr nm $ APIUnmuteUser userId
+ HideUser viewPwd -> withUser $ \User {userId} -> processChatCommand cxt nm $ APIHideUser userId viewPwd
+ UnhideUser viewPwd -> withUser $ \User {userId} -> processChatCommand cxt nm $ APIUnhideUser userId viewPwd
+ MuteUser -> withUser $ \User {userId} -> processChatCommand cxt nm $ APIMuteUser userId
+ UnmuteUser -> withUser $ \User {userId} -> processChatCommand cxt nm $ APIUnmuteUser userId
SetClientService userId' name enable -> checkChatStopped $ withUser' $ \currUser@User {userId} -> do
user@User {agentUserId = AgentUserId auId, clientService, profile = LocalProfile {displayName}} <-
if userId == userId' then pure currUser else privateGetUser userId'
@@ -544,7 +544,7 @@ processChatCommand vr nm = \case
ExportArchive -> do
ts <- liftIO getCurrentTime
let filePath = "simplex-chat." <> formatTime defaultTimeLocale "%FT%H%M%SZ" ts <> ".zip"
- processChatCommand vr nm $ APIExportArchive $ ArchiveConfig filePath Nothing Nothing
+ processChatCommand cxt nm $ APIExportArchive $ ArchiveConfig filePath Nothing Nothing
APIImportArchive cfg -> checkChatStopped $ do
fileErrs <- lift $ importArchive cfg
setStoreChanged
@@ -573,16 +573,16 @@ processChatCommand vr nm = \case
tags <- withFastStore' (`getUserChatTags` user)
pure $ CRChatTags user tags
APIGetChats {userId, pendingConnections, pagination, query} -> withUserId' userId $ \user -> do
- (errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db vr user pendingConnections pagination query)
+ (errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db cxt user pendingConnections pagination query)
unless (null errs) $ toView $ CEvtChatErrors (map ChatErrorStore errs)
pure $ CRApiChats user previews
APIGetChat (ChatRef cType cId scope_) contentFilter pagination search -> withUser $ \user -> case cType of
-- TODO optimize queries calculating ChatStats, currently they're disabled
CTDirect -> do
- (directChat, navInfo) <- withFastStore (\db -> getDirectChat db vr user cId contentFilter pagination search)
+ (directChat, navInfo) <- withFastStore (\db -> getDirectChat db cxt user cId contentFilter pagination search)
pure $ CRApiChat user (AChat SCTDirect directChat) navInfo
CTGroup -> do
- (groupChat, navInfo) <- withFastStore (\db -> getGroupChat db vr user cId scope_ contentFilter pagination search)
+ (groupChat, navInfo) <- withFastStore (\db -> getGroupChat db cxt user cId scope_ contentFilter pagination search)
groupChat' <- checkSupportChatAttention user groupChat
pure $ CRApiChat user (AChat SCTGroup groupChat') navInfo
CTLocal -> do
@@ -598,7 +598,7 @@ processChatCommand vr nm = \case
case correctedMemAttention (groupMemberId' scopeMem) suppChat chatItems of
Just newMemAttention -> do
(gInfo', scopeMem') <-
- withFastStore' $ \db -> setSupportChatMemberAttention db vr user gInfo scopeMem newMemAttention
+ withFastStore' $ \db -> setSupportChatMemberAttention db cxt user gInfo scopeMem newMemAttention
pure (groupChat {chatInfo = GroupChat gInfo' (Just $ GCSIMemberSupport (Just scopeMem'))} :: Chat 'CTGroup)
Nothing -> pure groupChat
_ -> pure groupChat
@@ -615,11 +615,11 @@ processChatCommand vr nm = \case
APIGetChatContentTypes chatRef -> withUser $ \user ->
CRChatContentTypes <$> withStore (\db -> getChatContentTypes db user chatRef)
APIGetChatItems pagination search -> withUser $ \user -> do
- chatItems <- withFastStore $ \db -> getAllChatItems db vr user pagination search
+ chatItems <- withFastStore $ \db -> getAllChatItems db cxt user pagination search
pure $ CRChatItems user Nothing chatItems
APIGetChatItemInfo chatRef itemId -> withUser $ \user -> do
(aci@(AChatItem cType dir _ ci), versions) <- withFastStore $ \db ->
- (,) <$> getAChatItem db vr user chatRef itemId <*> liftIO (getChatItemVersions db itemId)
+ (,) <$> getAChatItem db cxt user chatRef itemId <*> liftIO (getChatItemVersions db itemId)
let itemVersions = if null versions then maybeToList $ mkItemVersion ci else versions
memberDeliveryStatuses <- case (cType, dir) of
(SCTGroup, SMDSnd) -> L.nonEmpty <$> withFastStore' (`getGroupSndStatuses` itemId)
@@ -630,10 +630,10 @@ processChatCommand vr nm = \case
getForwardedFromItem :: User -> ChatItem c d -> CM (Maybe AChatItem)
getForwardedFromItem user ChatItem {meta = CIMeta {itemForwarded}} = case itemForwarded of
Just (CIFFContact _ _ (Just ctId) (Just fwdItemId)) ->
- Just <$> withFastStore (\db -> getAChatItem db vr user (ChatRef CTDirect ctId Nothing) fwdItemId)
+ Just <$> withFastStore (\db -> getAChatItem db cxt user (ChatRef CTDirect ctId Nothing) fwdItemId)
Just (CIFFGroup _ _ (Just gId) (Just fwdItemId)) ->
-- TODO [knocking] getAChatItem doesn't differentiate how to read based on scope - it should, instead of using group filter
- Just <$> withFastStore (\db -> getAChatItem db vr user (ChatRef CTGroup gId Nothing) fwdItemId)
+ Just <$> withFastStore (\db -> getAChatItem db cxt user (ChatRef CTGroup gId Nothing) fwdItemId)
_ -> pure Nothing
APISendMessages sendRef live itemTTL cms -> withUser $ \user -> mapM_ assertAllowedContent' cms >> case sendRef of
SRDirect chatId -> do
@@ -646,7 +646,7 @@ processChatCommand vr nm = \case
Nothing -> pure ()
withGroupLock "sendMessage" chatId $ do
(gInfo, cmrs) <- withFastStore $ \db -> do
- g <- getGroupInfo db vr user chatId
+ g <- getGroupInfo db cxt user chatId
(g,) <$> mapM (composedMessageReqMentions db user g) cms
sendGroupContentMessages user gInfo gsScope asGroup live itemTTL cmrs
APICreateChatTag (ChatTagData emoji text) -> withUser $ \user -> withFastStore' $ \db -> do
@@ -674,18 +674,18 @@ processChatCommand vr nm = \case
createNoteFolderContentItems user folderId (L.map composedMessageReq cms)
APIReportMessage gId reportedItemId reportReason reportText -> withUser $ \user ->
withGroupLock "reportMessage" gId $ do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user gId
let mc = MCReport reportText reportReason
cm = ComposedMessage {fileSource = Nothing, quotedItemId = Just reportedItemId, msgContent = mc, mentions = M.empty}
sendGroupContentMessages user gInfo (Just $ GCSMemberSupport Nothing) False False Nothing [composedMessageReq cm]
ReportMessage {groupName, contactName_, reportReason, reportedMessage} -> withUser $ \user -> do
gId <- withFastStore $ \db -> getGroupIdByName db user groupName
reportedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user gId contactName_ reportedMessage
- processChatCommand vr nm $ APIReportMessage gId reportedItemId reportReason ""
+ processChatCommand cxt nm $ APIReportMessage gId reportedItemId reportReason ""
APIUpdateChatItem (ChatRef cType chatId scope) itemId live (UpdatedMessage mc mentions) -> withUser $ \user -> assertAllowedContent mc >> case cType of
CTDirect -> withContactLock "updateChatItem" chatId $ do
unless (null mentions) $ throwCmdError "mentions are not supported in this chat"
- ct@Contact {contactId} <- withFastStore $ \db -> getContact db vr user chatId
+ ct@Contact {contactId} <- withFastStore $ \db -> getContact db cxt user chatId
assertDirectAllowed user MDSnd ct XMsgUpdate_
cci <- withFastStore $ \db -> getDirectCIWithReactions db user ct itemId
case cci of
@@ -709,7 +709,7 @@ processChatCommand vr nm = \case
_ -> throwChatError CEInvalidChatItemUpdate
CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate
CTGroup -> withGroupLock "updateChatItem" chatId $ do
- gInfo@GroupInfo {groupId, membership} <- withFastStore $ \db -> getGroupInfo db vr user chatId
+ gInfo@GroupInfo {groupId, membership} <- withFastStore $ \db -> getGroupInfo db cxt user chatId
when (isNothing scope) $ assertUserGroupRole gInfo GRAuthor
let (_, ft_) = msgContentTexts mc
if prohibitedSimplexLinks gInfo membership mc ft_
@@ -721,8 +721,8 @@ processChatCommand vr nm = \case
CChatItem SMDSnd ci@ChatItem {meta = CIMeta {itemSharedMsgId, itemTimed, itemLive, editable, showGroupAsSender}, content = ciContent} -> do
case (ciContent, itemSharedMsgId, editable) of
(CISndMsgContent oldMC, Just itemSharedMId, True) -> do
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
- recipients <- getGroupRecipients vr user gInfo chatScopeInfo groupKnockingVersion
+ chatScopeInfo <- mapM (getChatScopeInfo cxt user) scope
+ recipients <- getGroupRecipients cxt user gInfo chatScopeInfo groupKnockingVersion
let changed = mc /= oldMC
if changed || fromMaybe False itemLive
then do
@@ -778,7 +778,7 @@ processChatCommand vr nm = \case
CTGroup -> withGroupLock "deleteChatItem" chatId $ do
(gInfo, items) <- getCommandGroupChatItems user chatId itemIds
-- TODO [knocking] check scope for all items?
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
+ chatScopeInfo <- mapM (getChatScopeInfo cxt user) scope
deletions <- case mode of
CIDMInternal
| publicGroupEditor gInfo (membership gInfo) -> throwChatError CEInvalidChatItemDelete
@@ -786,7 +786,7 @@ processChatCommand vr nm = \case
CIDMInternalMark -> do
markGroupCIsDeleted user gInfo chatScopeInfo items Nothing =<< liftIO getCurrentTime
CIDMBroadcast -> do
- recipients <- getGroupRecipients vr user gInfo chatScopeInfo groupKnockingVersion
+ recipients <- getGroupRecipients cxt user gInfo chatScopeInfo groupKnockingVersion
assertDeletable items
assertUserGroupRole gInfo GRObserver -- can still delete messages sent earlier
let msgIds = itemsMsgIds items
@@ -795,7 +795,7 @@ processChatCommand vr nm = \case
delGroupChatItems user gInfo chatScopeInfo items False
CIDMHistory -> do
unless (publicGroupEditor gInfo (membership gInfo)) $ throwChatError CEInvalidChatItemDelete
- recipients <- getGroupRecipients vr user gInfo chatScopeInfo groupKnockingVersion
+ recipients <- getGroupRecipients cxt user gInfo chatScopeInfo groupKnockingVersion
let msgIds = itemsMsgIds items
events = L.nonEmpty $ map (\msgId -> XMsgDel msgId Nothing (toMsgScope gInfo <$> chatScopeInfo) True) msgIds
mapM_ (sendGroupMessages user gInfo Nothing False recipients) events
@@ -823,12 +823,12 @@ processChatCommand vr nm = \case
APIDeleteMemberChatItem gId itemIds -> withUser $ \user -> withGroupLock "deleteChatItem" gId $ do
(gInfo, items) <- getCommandGroupChatItems user gId itemIds
-- TODO [knocking] check scope is Nothing for all items? (prohibit moderation in support chats?)
- ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
+ ms <- withFastStore' $ \db -> getGroupMembers db cxt user gInfo
let recipients = filter memberCurrent ms
deletions <- delGroupChatItemsForMembers user gInfo Nothing recipients items
pure $ CRChatItemsDeleted user deletions True False
APIArchiveReceivedReports gId -> withUser $ \user -> withFastStore $ \db -> do
- g <- getGroupInfo db vr user gId
+ g <- getGroupInfo db cxt user gId
deleteTs <- liftIO getCurrentTime
ciIds <- liftIO $ markReceivedGroupReportsDeleted db user g deleteTs
pure $ CRGroupChatItemsDeleted user g ciIds True (Just $ membership g)
@@ -842,7 +842,7 @@ processChatCommand vr nm = \case
CIDMInternalMark -> markGroupCIsDeleted user gInfo Nothing items Nothing =<< liftIO getCurrentTime
CIDMHistory -> throwChatError CEInvalidChatItemDelete
CIDMBroadcast -> do
- ms <- withFastStore' $ \db -> getGroupModerators db vr user gInfo
+ ms <- withFastStore' $ \db -> getGroupModerators db cxt user gInfo
let recipients = filter memberCurrent ms
delGroupChatItemsForMembers user gInfo Nothing recipients items
pure $ CRChatItemsDeleted user deletions True False
@@ -853,7 +853,7 @@ processChatCommand vr nm = \case
APIChatItemReaction (ChatRef cType chatId scope) itemId add reaction -> withUser $ \user -> case cType of
CTDirect ->
withContactLock "chatItemReaction" chatId $
- withFastStore (\db -> (,) <$> getContact db vr user chatId <*> getDirectChatItem db user chatId itemId) >>= \case
+ withFastStore (\db -> (,) <$> getContact db cxt user chatId <*> getDirectChatItem db user chatId itemId) >>= \case
(ct, CChatItem md ci@ChatItem {meta = CIMeta {itemSharedMsgId = Just itemSharedMId}}) -> do
unless (featureAllowed SCFReactions forUser ct) $
throwCmdError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFReactions)
@@ -874,10 +874,10 @@ processChatCommand vr nm = \case
withGroupLock "chatItemReaction" chatId $ do
-- TODO [knocking] check chat item scope?
(g@GroupInfo {membership}, CChatItem md ci) <- withFastStore $ \db -> do
- g <- getGroupInfo db vr user chatId
+ g <- getGroupInfo db cxt user chatId
(g,) <$> getGroupCIWithReactions db user g itemId
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
- recipients <- getGroupRecipients vr user g chatScopeInfo groupKnockingVersion
+ chatScopeInfo <- mapM (getChatScopeInfo cxt user) scope
+ recipients <- getGroupRecipients cxt user g chatScopeInfo groupKnockingVersion
case ci of
ChatItem {meta = CIMeta {itemSharedMsgId = Just itemSharedMId}} -> do
unless (groupFeatureAllowed SGFReactions g) $
@@ -908,7 +908,7 @@ processChatCommand vr nm = \case
APIGetReactionMembers userId groupId itemId reaction -> withUserId userId $ \user -> do
memberReactions <- withStore $ \db -> do
CChatItem _ ChatItem {meta = CIMeta {itemSharedMsgId = Just itemSharedMId}} <- getGroupChatItem db user groupId itemId
- liftIO $ getReactionMembers db vr user groupId itemSharedMId reaction
+ liftIO $ getReactionMembers db cxt user groupId itemSharedMId reaction
pure $ CRReactionMembers user memberReactions
-- TODO [knocking] forward from scope?
APIPlanForwardChatItems (ChatRef fromCType fromChatId _scope) itemIds -> withUser $ \user -> case fromCType of
@@ -972,7 +972,7 @@ processChatCommand vr nm = \case
case L.nonEmpty cmrs of
Just cmrs' ->
withGroupLock "forwardChatItem, to group" toChatId $ do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user toChatId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user toChatId
sendGroupContentMessages user gInfo toScope sendAsGroup False itemTTL cmrs'
Nothing -> pure $ CRNewChatItems user []
CTLocal -> do
@@ -1106,7 +1106,7 @@ processChatCommand vr nm = \case
pure $ prefix <> formattedDate <> ext
APIShareChatMsgContent (ChatRef CTGroup groupId _) toSendRef -> withUser $ \user -> do
GroupInfo {groupProfile = gp@GroupProfile {publicGroup}, membership = GroupMember {memberId, memberRole}, groupKeys} <-
- withFastStore $ \db -> getGroupInfo db vr user groupId
+ withFastStore $ \db -> getGroupInfo db cxt user groupId
case publicGroup of
Nothing -> throwCmdError "not a public group"
Just PublicGroupProfile {groupLink} -> do
@@ -1128,11 +1128,11 @@ processChatCommand vr nm = \case
shareChatBinding :: User -> SendRef -> CM (Maybe (ChatBinding, ByteString))
shareChatBinding u = \case
SRDirect contactId -> do
- ct <- withFastStore $ \db -> getContact db vr u contactId
+ ct <- withFastStore $ \db -> getContact db cxt u contactId
forM (contactConn ct) $ \conn ->
(CBDirect,) <$> withAgent (`getConnectionRatchetAdHash` aConnId conn)
SRGroup toGroupId _ asGroup -> do
- GroupInfo {groupProfile = GroupProfile {publicGroup}, membership = m} <- withFastStore $ \db -> getGroupInfo db vr u toGroupId
+ GroupInfo {groupProfile = GroupProfile {publicGroup}, membership = m} <- withFastStore $ \db -> getGroupInfo db cxt u toGroupId
pure $ mkBinding m <$> publicGroup
where
mkBinding GroupMember {memberId} PublicGroupProfile {publicGroupId = pgId}
@@ -1140,7 +1140,7 @@ processChatCommand vr nm = \case
| otherwise = (CBGroup, smpEncode (pgId, memberId))
APIShareChatMsgContent _ _ -> throwCmdError "sharing is only supported for public groups"
APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user
- UserRead -> withUser $ \User {userId} -> processChatCommand vr nm $ APIUserRead userId
+ UserRead -> withUser $ \User {userId} -> processChatCommand cxt nm $ APIUserRead userId
APIChatRead chatRef@(ChatRef cType chatId scope_) -> withUser $ \_ -> case cType of
CTDirect -> do
user <- withFastStore $ \db -> getUserByContactId db chatId
@@ -1154,7 +1154,7 @@ processChatCommand vr nm = \case
CTGroup -> do
(user, gInfo) <- withFastStore $ \db -> do
user <- getUserByGroupId db chatId
- gInfo <- getGroupInfo db vr user chatId
+ gInfo <- getGroupInfo db cxt user chatId
pure (user, gInfo)
ts <- liftIO getCurrentTime
case scope_ of
@@ -1166,10 +1166,10 @@ processChatCommand vr nm = \case
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
ok user
Just scope -> do
- scopeInfo <- getChatScopeInfo vr user scope
+ scopeInfo <- getChatScopeInfo cxt user scope
(gInfo', m', timedItems) <- withFastStore' $ \db -> do
timedItems <- getGroupUnreadTimedItems db user chatId (Just scope)
- (gInfo', m') <- updateSupportChatItemsRead db vr user gInfo scopeInfo
+ (gInfo', m') <- updateSupportChatItemsRead db cxt user gInfo scopeInfo
timedItems' <- setGroupChatItemsDeleteAt db user chatId timedItems ts
pure (gInfo', m', timedItems')
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
@@ -1184,7 +1184,7 @@ processChatCommand vr nm = \case
CTDirect -> do
(user, ct) <- withFastStore $ \db -> do
user <- getUserByContactId db chatId
- ct <- getContact db vr user chatId
+ ct <- getContact db cxt user chatId
pure (user, ct)
timedItems <- withFastStore' $ \db -> do
timedItems <- updateDirectChatItemsReadList db user chatId itemIds
@@ -1194,11 +1194,11 @@ processChatCommand vr nm = \case
CTGroup -> do
(user, gInfo) <- withFastStore $ \db -> do
user <- getUserByGroupId db chatId
- gInfo <- getGroupInfo db vr user chatId
+ gInfo <- getGroupInfo db cxt user chatId
pure (user, gInfo)
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
+ chatScopeInfo <- mapM (getChatScopeInfo cxt user) scope
(timedItems, gInfo') <- withFastStore $ \db -> do
- (timedItems, gInfo') <- updateGroupChatItemsReadList db vr user gInfo chatScopeInfo itemIds
+ (timedItems, gInfo') <- updateGroupChatItemsReadList db cxt user gInfo chatScopeInfo itemIds
timedItems' <- liftIO $ setGroupChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime
pure (timedItems', gInfo')
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
@@ -1209,13 +1209,13 @@ processChatCommand vr nm = \case
APIChatUnread (ChatRef cType chatId scope) unreadChat -> withUser $ \user -> case cType of
CTDirect -> do
withFastStore $ \db -> do
- ct <- getContact db vr user chatId
+ ct <- getContact db cxt user chatId
liftIO $ updateContactUnreadChat db user ct unreadChat
ok user
-- TODO [knocking] set support chat as unread?
CTGroup | isNothing scope -> do
withFastStore $ \db -> do
- gInfo <- getGroupInfo db vr user chatId
+ gInfo <- getGroupInfo db cxt user chatId
liftIO $ updateGroupUnreadChat db user gInfo unreadChat
ok user
CTLocal -> do
@@ -1226,7 +1226,7 @@ processChatCommand vr nm = \case
_ -> throwCmdError "not supported"
APIDeleteChat cRef@(ChatRef cType chatId scope) cdm -> withUser $ \user@User {userId} -> case cType of
CTDirect -> do
- ct <- withFastStore $ \db -> getContact db vr user chatId
+ ct <- withFastStore $ \db -> getContact db cxt user chatId
filesInfo <- withFastStore' $ \db -> getContactFileInfo db user ct
withContactLock "deleteChat direct" chatId $
case cdm of
@@ -1246,17 +1246,17 @@ processChatCommand vr nm = \case
ct' <- withFastStore $ \db -> do
liftIO $ deleteContactConnections db user ct
liftIO $ void $ updateContactStatus db user ct CSDeletedByUser
- getContact db vr user chatId
+ getContact db cxt user chatId
pure $ CRContactDeleted user ct'
CDMMessages -> do
- void $ processChatCommand vr nm $ APIClearChat cRef
+ void $ processChatCommand cxt nm $ APIClearChat cRef
withFastStore' $ \db -> setContactChatDeleted db user ct True
pure $ CRContactDeleted user ct {chatDeleted = True}
where
sendDelDeleteConns ct notify = do
let doSendDel = contactReady ct && contactActive ct && notify
when doSendDel $ void (sendDirectContactMessage user ct XDirectDel) `catchAllErrors` const (pure ())
- contactConnIds <- map aConnId <$> withFastStore' (\db -> getContactConnections db vr userId ct)
+ contactConnIds <- map aConnId <$> withFastStore' (\db -> getContactConnections db cxt userId ct)
deleteAgentConnectionsAsync' contactConnIds doSendDel
CTContactConnection -> withConnectionLock "deleteChat contactConnection" chatId $ do
conn@PendingContactConnection {pccAgentConnId = AgentConnId acId} <- withFastStore $ \db -> getPendingContactConnection db userId chatId
@@ -1264,7 +1264,7 @@ processChatCommand vr nm = \case
withFastStore' $ \db -> deletePendingContactConnection db userId chatId
pure $ CRContactConnectionDeleted user conn
CTGroup | isNothing scope -> do
- gInfo@GroupInfo {membership} <- withFastStore $ \db -> getGroupInfo db vr user chatId
+ gInfo@GroupInfo {membership} <- withFastStore $ \db -> getGroupInfo db cxt user chatId
let isOwner = memberRole' membership == GROwner
canDelete = isOwner || not (memberCurrent membership)
unless canDelete $ throwChatError $ CEGroupUserRole gInfo GROwner
@@ -1288,25 +1288,25 @@ processChatCommand vr nm = \case
where
getRecipients gInfo
| useRelays' gInfo = do
- relays <- withFastStore' $ \db -> getGroupRelayMembers db vr user gInfo
+ relays <- withFastStore' $ \db -> getGroupRelayMembers db cxt user gInfo
pure (relays, relays)
| otherwise = do
- ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
+ ms <- withFastStore' $ \db -> getGroupMembers db cxt user gInfo
pure (ms, filter memberCurrentOrPending ms)
_ -> throwCmdError "not supported"
APIClearChat (ChatRef cType chatId scope) -> withUser $ \user@User {userId} -> case cType of
CTDirect -> do
- ct <- withFastStore $ \db -> getContact db vr user chatId
+ ct <- withFastStore $ \db -> getContact db cxt user chatId
filesInfo <- withFastStore' $ \db -> getContactFileInfo db user ct
deleteCIFiles user filesInfo
withFastStore' $ \db -> deleteContactCIs db user ct
pure $ CRChatCleared user (AChatInfo SCTDirect $ DirectChat ct)
CTGroup | isNothing scope -> do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user chatId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user chatId
filesInfo <- withFastStore' $ \db -> getGroupFileInfo db user gInfo
deleteCIFiles user filesInfo
withFastStore' $ \db -> deleteGroupChatItemsMessages db user gInfo
- membersToDelete <- withFastStore' $ \db -> getGroupMembersForExpiration db vr user gInfo
+ membersToDelete <- withFastStore' $ \db -> getGroupMembersForExpiration db cxt user gInfo
forM_ membersToDelete $ \m -> withFastStore' $ \db -> deleteGroupMember db user m
pure $ CRChatCleared user (AChatInfo SCTGroup $ GroupChat gInfo Nothing)
CTLocal -> do
@@ -1367,7 +1367,7 @@ processChatCommand vr nm = \case
withFastStore $ \db -> do
cReq@UserContactRequest {contactId_} <- getContactRequest db user connReqId
ct_ <- forM contactId_ $ \contactId -> do
- ct <- getContact db vr user contactId
+ ct <- getContact db cxt user contactId
deleteContact db user ct
pure ct
liftIO $ deleteContactRequest db user connReqId
@@ -1376,7 +1376,7 @@ processChatCommand vr nm = \case
pure $ CRContactRequestRejected user cReq ct_
APISendCallInvitation contactId callType -> withUser $ \user -> do
-- party initiating call
- ct <- withFastStore $ \db -> getContact db vr user contactId
+ ct <- withFastStore $ \db -> getContact db cxt user contactId
assertDirectAllowed user MDSnd ct XCallInv_
if featureAllowed SCFCalls forUser ct
then do
@@ -1398,7 +1398,7 @@ processChatCommand vr nm = \case
else throwCmdError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFCalls)
SendCallInvitation cName callType -> withUser $ \user -> do
contactId <- withFastStore $ \db -> getContactIdByName db user cName
- processChatCommand vr nm $ APISendCallInvitation contactId callType
+ processChatCommand cxt nm $ APISendCallInvitation contactId callType
APIRejectCall contactId ->
-- party accepting call
withCurrentCall contactId $ \user ct Call {chatItemId, callState} -> case callState of
@@ -1465,23 +1465,23 @@ processChatCommand vr nm = \case
_ -> Nothing
rcvCallInvitation (contactId, callUUID, callTs, peerCallType, sharedKey) = runExceptT . withFastStore $ \db -> do
user <- getUserByContactId db contactId
- contact <- getContact db vr user contactId
+ contact <- getContact db cxt user contactId
pure RcvCallInvitation {user, contact, callType = peerCallType, sharedKey, callUUID, callTs}
APICallStatus contactId receivedStatus ->
withCurrentCall contactId $ \user ct call ->
updateCallItemStatus user ct call receivedStatus Nothing $> Just call
APIUpdateProfile userId profile -> withUserId userId (`updateProfile` profile)
APISetContactPrefs contactId prefs' -> withUser $ \user -> do
- ct <- withFastStore $ \db -> getContact db vr user contactId
+ ct <- withFastStore $ \db -> getContact db cxt user contactId
updateContactPrefs user ct prefs'
APISetContactAlias contactId localAlias -> withUser $ \user@User {userId} -> do
ct' <- withFastStore $ \db -> do
- ct <- getContact db vr user contactId
+ ct <- getContact db cxt user contactId
liftIO $ updateContactAlias db userId ct localAlias
pure $ CRContactAliasUpdated user ct'
APISetGroupAlias gId localAlias -> withUser $ \user@User {userId} -> do
gInfo' <- withFastStore $ \db -> do
- gInfo <- getGroupInfo db vr user gId
+ gInfo <- getGroupInfo db cxt user gId
liftIO $ updateGroupAlias db userId gInfo localAlias
pure $ CRGroupAliasUpdated user gInfo'
APISetConnectionAlias connId localAlias -> withUser $ \user@User {userId} -> do
@@ -1499,23 +1499,23 @@ processChatCommand vr nm = \case
APISetChatUIThemes (ChatRef cType chatId scope) uiThemes -> withUser $ \user -> case cType of
CTDirect -> do
withFastStore $ \db -> do
- ct <- getContact db vr user chatId
+ ct <- getContact db cxt user chatId
liftIO $ setContactUIThemes db user ct uiThemes
ok user
CTGroup | isNothing scope -> do
withFastStore $ \db -> do
- g <- getGroupInfo db vr user chatId
+ g <- getGroupInfo db cxt user chatId
liftIO $ setGroupUIThemes db user g uiThemes
ok user
_ -> throwCmdError "not supported"
APISetGroupCustomData groupId customData_ -> withUser $ \user -> do
withFastStore $ \db -> do
- g <- getGroupInfo db vr user groupId
+ g <- getGroupInfo db cxt user groupId
liftIO $ setGroupCustomData db user g customData_
ok user
APISetContactCustomData contactId customData_ -> withUser $ \user -> do
withFastStore $ \db -> do
- ct <- getContact db vr user contactId
+ ct <- getContact db cxt user contactId
liftIO $ setContactCustomData db user ct customData_
ok user
APIGetNtfToken -> withUser' $ \_ -> crNtfToken <$> withAgent getNtfToken
@@ -1536,7 +1536,7 @@ processChatCommand vr nm = \case
let agentConnId = AgentConnId ntfConnId
mkNtfConn user connEntity = NtfConn {user, agentConnId, agentDbQueueId = ntfDbQueueId, connEntity, expectedMsg_ = expectedMsgInfo <$> nMsgMeta}
getUserByAConnId db agentConnId
- $>>= \user -> fmap (mkNtfConn user) . eitherToMaybe <$> runExceptT (getConnectionEntity db vr user agentConnId)
+ $>>= \user -> fmap (mkNtfConn user) . eitherToMaybe <$> runExceptT (getConnectionEntity db cxt user agentConnId)
APIGetConnNtfMessages connMsgs -> withUser $ \_ -> do
msgs <- lift $ withAgent' (`getConnectionMessages` connMsgs)
let ntfMsgs = L.map receivedMsgInfo msgs
@@ -1552,7 +1552,7 @@ processChatCommand vr nm = \case
[] -> throwCmdError "no servers"
_ -> do
srvs' <- mapM aUserServer srvs
- processChatCommand vr nm $ APISetUserServers userId $ L.map (updatedServers p srvs') userServers
+ processChatCommand cxt nm $ APISetUserServers userId $ L.map (updatedServers p srvs') userServers
where
aUserServer :: AProtoServerWithAuth -> CM (AUserServer p)
aUserServer (AProtoServerWithAuth p' srv) = case testEquality p p' of
@@ -1561,7 +1561,7 @@ processChatCommand vr nm = \case
APITestProtoServer userId srv@(AProtoServerWithAuth _ server) -> withUserId userId $ \user ->
lift $ CRServerTestResult user srv <$> withAgent' (\a -> testProtocolServer a nm (aUserId user) server)
TestProtoServer srv -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APITestProtoServer userId srv
+ processChatCommand cxt nm $ APITestProtoServer userId srv
APITestChatRelay userId address -> withUserId userId $ \user -> do
let failAt step e = pure $ CRChatRelayTestResult user Nothing (Just $ RelayTestFailure step e)
r <- tryAllErrors $ getShortLinkConnReq nm user address
@@ -1581,7 +1581,7 @@ processChatCommand vr nm = \case
subMode <- chatReadVar subscriptionMode
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff
conn@Connection {connId = testCId} <- withFastStore $ \db ->
- createRelayTestConnection db vr user connId ConnPrepared chatV subMode
+ createRelayTestConnection db cxt user connId ConnPrepared chatV subMode
challenge <- drgRandomBytes 32
testVar <- newEmptyTMVarIO
let acId = aConnId conn
@@ -1601,9 +1601,9 @@ processChatCommand vr nm = \case
Right (Just Nothing) -> pure $ CRChatRelayTestResult user (Just relayProfile) Nothing
Right (Just (Just failure)) -> pure $ CRChatRelayTestResult user (Just relayProfile) (Just failure)
TestChatRelay address -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APITestChatRelay userId address
+ processChatCommand cxt nm $ APITestChatRelay userId address
APIAllowRelayGroup groupId -> withUser $ \user -> do
- gInfo' <- withStore $ \db -> allowRelayGroup db vr user groupId
+ gInfo' <- withStore $ \db -> allowRelayGroup db cxt user groupId
pure $ CRRelayGroupAllowed user gInfo'
GetUserChatRelays -> withUser $ \user -> do
srvs <- withFastStore (`getUserServers` user)
@@ -1616,7 +1616,7 @@ processChatCommand vr nm = \case
[] -> throwCmdError "no relays"
_ -> do
let relays' = map aUserRelay relays
- processChatCommand vr nm $ APISetUserServers userId $ L.map (updatedRelays relays') userServers
+ processChatCommand cxt nm $ APISetUserServers userId $ L.map (updatedRelays relays') userServers
where
aUserRelay :: CLINewRelay -> AUserChatRelay
aUserRelay CLINewRelay {address, name} = AUCR SDBNew $ newChatRelay (mkRelayProfile name Nothing) [""] address
@@ -1645,7 +1645,7 @@ processChatCommand vr nm = \case
SetServerOperators operatorsRoles -> do
ops <- serverOperators <$> withFastStore getServerOperators
ops' <- mapM (updateOp ops) operatorsRoles
- processChatCommand vr nm $ APISetServerOperators ops'
+ processChatCommand cxt nm $ APISetServerOperators ops'
where
updateOp :: [ServerOperator] -> ServerOperatorRoles -> CM ServerOperator
updateOp ops r =
@@ -1710,14 +1710,14 @@ processChatCommand vr nm = \case
expireChat user globalTTL = do
currentTs <- liftIO getCurrentTime
case cType of
- CTDirect -> expireContactChatItems user vr globalTTL chatId
+ CTDirect -> expireContactChatItems user cxt globalTTL chatId
CTGroup | isNothing scope ->
let createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs
- in expireGroupChatItems user vr globalTTL createdAtCutoff chatId
+ in expireGroupChatItems user cxt globalTTL createdAtCutoff chatId
_ -> throwCmdError "not supported"
SetChatTTL chatName newTTL -> withUser' $ \user@User {userId} -> do
chatRef <- getChatRef user chatName
- processChatCommand vr nm $ APISetChatTTL userId chatRef newTTL
+ processChatCommand cxt nm $ APISetChatTTL userId chatRef newTTL
GetChatTTL chatName -> withUser' $ \user -> do
-- TODO [knocking] support scope in CLI apis
ChatRef cType chatId _ <- getChatRef user chatName
@@ -1737,18 +1737,18 @@ processChatCommand vr nm = \case
lift $ setChatItemsExpiration user newTTL ttlCount
ok user
SetChatItemTTL newTTL_ -> withUser' $ \User {userId} -> do
- processChatCommand vr nm $ APISetChatItemTTL userId newTTL_
+ processChatCommand cxt nm $ APISetChatItemTTL userId newTTL_
APIGetChatItemTTL userId -> withUserId' userId $ \user -> do
ttl <- withFastStore' (`getChatItemTTL` user)
pure $ CRChatItemTTL user (Just ttl)
GetChatItemTTL -> withUser' $ \User {userId} -> do
- processChatCommand vr nm $ APIGetChatItemTTL userId
+ processChatCommand cxt nm $ APIGetChatItemTTL userId
APISetNetworkConfig cfg -> withUser' $ \_ -> withAgent (`setNetworkConfig` cfg) >> ok_
APIGetNetworkConfig -> withUser' $ \_ ->
CRNetworkConfig <$> lift getNetworkConfig
SetNetworkConfig simpleNetCfg -> do
cfg <- (`updateNetworkConfig` simpleNetCfg) <$> lift getNetworkConfig
- void . processChatCommand vr nm $ APISetNetworkConfig cfg
+ void . processChatCommand cxt nm $ APISetNetworkConfig cfg
pure $ CRNetworkConfig cfg
APISetNetworkInfo info -> lift (withAgent' (`setUserNetworkInfo` info)) >> ok_
ReconnectAllServers -> withUser' $ \_ -> lift (withAgent' reconnectAllServers) >> ok_
@@ -1758,7 +1758,7 @@ processChatCommand vr nm = \case
APISetChatSettings (ChatRef cType chatId scope) chatSettings -> withUser $ \user -> case cType of
CTDirect -> do
ct <- withFastStore $ \db -> do
- ct <- getContact db vr user chatId
+ ct <- getContact db cxt user chatId
liftIO $ updateContactSettings db user chatId chatSettings
pure ct
forM_ (contactConnId ct) $ \connId ->
@@ -1766,7 +1766,7 @@ processChatCommand vr nm = \case
ok user
CTGroup | isNothing scope -> do
ms <- withFastStore $ \db -> do
- gInfo <- getGroupInfo db vr user chatId
+ gInfo <- getGroupInfo db cxt user chatId
ms <- liftIO $ getMembers db gInfo
liftIO $ updateGroupSettings db user chatId chatSettings
pure ms
@@ -1775,19 +1775,19 @@ processChatCommand vr nm = \case
ok user
where
getMembers db gInfo
- | useRelays' gInfo = getGroupRelayMembers db vr user gInfo
- | otherwise = getGroupMembers db vr user gInfo
+ | useRelays' gInfo = getGroupRelayMembers db cxt user gInfo
+ | otherwise = getGroupMembers db cxt user gInfo
_ -> throwCmdError "not supported"
APISetMemberSettings gId gMemberId settings -> withUser $ \user -> do
m <- withFastStore $ \db -> do
liftIO $ updateGroupMemberSettings db user gId gMemberId settings
- getGroupMember db vr user gId gMemberId
+ getGroupMember db cxt user gId gMemberId
let ntfOn = not (memberBlocked m)
toggleNtf m ntfOn
ok user
APIContactInfo contactId -> withUser $ \user@User {userId} -> do
-- [incognito] print user's incognito profile for this contact
- ct@Contact {activeConn} <- withFastStore $ \db -> getContact db vr user contactId
+ ct@Contact {activeConn} <- withFastStore $ \db -> getContact db cxt user contactId
incognitoProfile <- case activeConn of
Nothing -> pure Nothing
Just Connection {customUserProfileId} ->
@@ -1795,14 +1795,14 @@ processChatCommand vr nm = \case
connectionStats <- mapM (withAgent . flip getConnectionServers) (contactConnId ct)
pure $ CRContactInfo user ct connectionStats (fmap fromLocalProfile incognitoProfile)
APIContactQueueInfo contactId -> withUser $ \user -> do
- ct@Contact {activeConn} <- withFastStore $ \db -> getContact db vr user contactId
+ ct@Contact {activeConn} <- withFastStore $ \db -> getContact db cxt user contactId
case activeConn of
Just conn -> getConnQueueInfo user conn
Nothing -> throwChatError $ CEContactNotActive ct
APIGroupInfo gId -> withUser $ \user ->
- CRGroupInfo user <$> withFastStore (\db -> getGroupInfo db vr user gId)
+ CRGroupInfo user <$> withFastStore (\db -> getGroupInfo db cxt user gId)
APIGetUpdatedGroupLinkData groupId -> withUser $ \user -> do
- gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} <- withFastStore $ \db -> getGroupInfo db cxt user groupId
case p of
GroupProfile {publicGroup = Just PublicGroupProfile {groupLink = sLnk}} | useRelays' gInfo -> do
(_, cData@(ContactLinkData _ UserContactData {relays = currentRelayLinks})) <- getShortLinkConnReq' nm user sLnk
@@ -1816,44 +1816,44 @@ processChatCommand vr nm = \case
pure $ CRGroupInfo user gInfo'
_ -> throwCmdError "group link data not available"
APIGroupMemberInfo gId gMemberId -> withUser $ \user -> do
- (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
+ (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db cxt user gId <*> getGroupMember db cxt user gId gMemberId
connectionStats <- mapM (withAgent . flip getConnectionServers) (memberConnId m)
pure $ CRGroupMemberInfo user g m connectionStats
APIGroupMemberQueueInfo gId gMemberId -> withUser $ \user -> do
- GroupMember {activeConn} <- withFastStore $ \db -> getGroupMember db vr user gId gMemberId
+ GroupMember {activeConn} <- withFastStore $ \db -> getGroupMember db cxt user gId gMemberId
case activeConn of
Just conn -> getConnQueueInfo user conn
Nothing -> throwChatError CEGroupMemberNotActive
APISwitchContact contactId -> withUser $ \user -> do
- ct <- withFastStore $ \db -> getContact db vr user contactId
+ ct <- withFastStore $ \db -> getContact db cxt user contactId
case contactConnId ct of
Just connId -> do
connectionStats <- withAgent $ \a -> switchConnectionAsync a "" connId
pure $ CRContactSwitchStarted user ct connectionStats
Nothing -> throwChatError $ CEContactNotActive ct
APISwitchGroupMember gId gMemberId -> withUser $ \user -> do
- (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
+ (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db cxt user gId <*> getGroupMember db cxt user gId gMemberId
case memberConnId m of
Just connId -> do
connectionStats <- withAgent (\a -> switchConnectionAsync a "" connId)
pure $ CRGroupMemberSwitchStarted user g m connectionStats
_ -> throwChatError CEGroupMemberNotActive
APIAbortSwitchContact contactId -> withUser $ \user -> do
- ct <- withFastStore $ \db -> getContact db vr user contactId
+ ct <- withFastStore $ \db -> getContact db cxt user contactId
case contactConnId ct of
Just connId -> do
connectionStats <- withAgent $ \a -> abortConnectionSwitch a connId
pure $ CRContactSwitchAborted user ct connectionStats
Nothing -> throwChatError $ CEContactNotActive ct
APIAbortSwitchGroupMember gId gMemberId -> withUser $ \user -> do
- (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
+ (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db cxt user gId <*> getGroupMember db cxt user gId gMemberId
case memberConnId m of
Just connId -> do
connectionStats <- withAgent $ \a -> abortConnectionSwitch a connId
pure $ CRGroupMemberSwitchAborted user g m connectionStats
_ -> throwChatError CEGroupMemberNotActive
APISyncContactRatchet contactId force -> withUser $ \user -> withContactLock "syncContactRatchet" contactId $ do
- ct <- withFastStore $ \db -> getContact db vr user contactId
+ ct <- withFastStore $ \db -> getContact db cxt user contactId
case contactConn ct of
Just conn@Connection {pqSupport} -> do
cStats@ConnectionStats {ratchetSyncState = rss} <- withAgent $ \a -> synchronizeRatchet a (aConnId conn) pqSupport force
@@ -1861,7 +1861,7 @@ processChatCommand vr nm = \case
pure $ CRContactRatchetSyncStarted user ct cStats
Nothing -> throwChatError $ CEContactNotActive ct
APISyncGroupMemberRatchet gId gMemberId force -> withUser $ \user -> withGroupLock "syncGroupMemberRatchet" gId $ do
- (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
+ (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db cxt user gId <*> getGroupMember db cxt user gId gMemberId
case memberConnId m of
Just connId -> do
cStats@ConnectionStats {ratchetSyncState = rss} <- withAgent $ \a -> synchronizeRatchet a connId PQSupportOff force
@@ -1870,7 +1870,7 @@ processChatCommand vr nm = \case
pure $ CRGroupMemberRatchetSyncStarted user g' m' cStats
_ -> throwChatError CEGroupMemberNotActive
APIGetContactCode contactId -> withUser $ \user -> do
- ct@Contact {activeConn} <- withFastStore $ \db -> getContact db vr user contactId
+ ct@Contact {activeConn} <- withFastStore $ \db -> getContact db cxt user contactId
case activeConn of
Just conn@Connection {connId} -> do
code <- getConnectionCode $ aConnId conn
@@ -1884,7 +1884,7 @@ processChatCommand vr nm = \case
pure $ CRContactCode user ct' code
Nothing -> throwChatError $ CEContactNotActive ct
APIGetGroupMemberCode gId gMemberId -> withUser $ \user -> do
- (g, m@GroupMember {activeConn}) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
+ (g, m@GroupMember {activeConn}) <- withFastStore $ \db -> (,) <$> getGroupInfo db cxt user gId <*> getGroupMember db cxt user gId gMemberId
case activeConn of
Just conn@Connection {connId} -> do
code <- getConnectionCode $ aConnId conn
@@ -1898,24 +1898,24 @@ processChatCommand vr nm = \case
pure $ CRGroupMemberCode user g m' code
_ -> throwChatError CEGroupMemberNotActive
APIVerifyContact contactId code -> withUser $ \user -> do
- ct@Contact {activeConn} <- withFastStore $ \db -> getContact db vr user contactId
+ ct@Contact {activeConn} <- withFastStore $ \db -> getContact db cxt user contactId
case activeConn of
Just conn -> verifyConnectionCode user conn code
Nothing -> throwChatError $ CEContactNotActive ct
APIVerifyGroupMember gId gMemberId code -> withUser $ \user -> do
- GroupMember {activeConn} <- withFastStore $ \db -> getGroupMember db vr user gId gMemberId
+ GroupMember {activeConn} <- withFastStore $ \db -> getGroupMember db cxt user gId gMemberId
case activeConn of
Just conn -> verifyConnectionCode user conn code
_ -> throwChatError CEGroupMemberNotActive
APIEnableContact contactId -> withUser $ \user -> do
- ct@Contact {activeConn} <- withFastStore $ \db -> getContact db vr user contactId
+ ct@Contact {activeConn} <- withFastStore $ \db -> getContact db cxt user contactId
case activeConn of
Just conn -> do
withFastStore' $ \db -> setAuthErrCounter db user conn 0
ok user
Nothing -> throwChatError $ CEContactNotActive ct
APIEnableGroupMember gId gMemberId -> withUser $ \user -> do
- GroupMember {activeConn} <- withFastStore $ \db -> getGroupMember db vr user gId gMemberId
+ GroupMember {activeConn} <- withFastStore $ \db -> getGroupMember db cxt user gId gMemberId
case activeConn of
Just conn -> do
withFastStore' $ \db -> setAuthErrCounter db user conn 0
@@ -1925,16 +1925,16 @@ processChatCommand vr nm = \case
SetSendReceipts cName rcptsOn_ -> updateChatSettings cName (\cs -> cs {sendRcpts = rcptsOn_})
SetShowMemberMessages gName mName showMessages -> withUser $ \user -> do
(gId, mId) <- getGroupAndMemberId user gName mName
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId
- m <- withFastStore $ \db -> getGroupMember db vr user gId mId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user gId
+ m <- withFastStore $ \db -> getGroupMember db cxt user gId mId
let GroupInfo {membership = GroupMember {memberRole = membershipRole}} = gInfo
when (membershipRole >= GRModerator) $ throwChatError $ CECantBlockMemberForSelf gInfo m showMessages
let settings = (memberSettings m) {showMessages}
- processChatCommand vr nm $ APISetMemberSettings gId mId settings
+ processChatCommand cxt nm $ APISetMemberSettings gId mId settings
ContactInfo cName -> withContactName cName APIContactInfo
ShowGroupInfo gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APIGroupInfo groupId
+ processChatCommand cxt nm $ APIGroupInfo groupId
GroupMemberInfo gName mName -> withMemberName gName mName APIGroupMemberInfo
ContactQueueInfo cName -> withContactName cName APIContactQueueInfo
GroupMemberQueueInfo gName mName -> withMemberName gName mName APIGroupMemberQueueInfo
@@ -1964,7 +1964,7 @@ processChatCommand vr nm = \case
conn <- withFastStore' $ \db -> createDirectConnection db user connId ccLink' Nothing ConnNew incognitoProfile subMode initialChatVersion PQSupportOn
pure $ CRInvitation user ccLink' conn
AddContact incognito -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APIAddContact userId incognito
+ processChatCommand cxt nm $ APIAddContact userId incognito
APISetConnectionIncognito connId incognito -> withUser $ \user@User {userId} -> do
conn <- withFastStore $ \db -> getPendingContactConnection db userId connId
let PendingContactConnection {pccConnStatus, customUserProfileId} = conn
@@ -2021,7 +2021,7 @@ processChatCommand vr nm = \case
groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences
groupProfile = businessGroupProfile profile groupPreferences
gVar <- asks random
- (gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar vr user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing Nothing
+ (gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing Nothing
hostMember <- maybe (throwCmdError "no host member") pure hostMember_
void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing (Just epochStart)
let cd = CDGroupRcv gInfo Nothing hostMember
@@ -2034,7 +2034,7 @@ processChatCommand vr nm = \case
_ -> Chat cInfo [] emptyChatStats
pure $ CRNewPreparedChat user $ AChat SCTGroup chat
ACCL _ (CCLink cReq _) -> do
- (ct, displaced_) <- withStore $ \db -> createPreparedContact db vr user profile accLink welcomeSharedMsgId Nothing
+ (ct, displaced_) <- withStore $ \db -> createPreparedContact db cxt user profile accLink welcomeSharedMsgId Nothing
let Profile {simplexName = pSimplexName} = profile
Contact {localDisplayName = newLDN} = ct
surfaceSimplexNameConflict user pSimplexName displaced_ SNCEContact newLDN
@@ -2056,7 +2056,7 @@ processChatCommand vr nm = \case
let useRelays = not direct
subRole <- if useRelays then asks $ channelSubscriberRole . config else pure GRMember
gVar <- asks random
- (gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar vr user gp False ccLink welcomeSharedMsgId useRelays subRole publicMemberCount_ Nothing
+ (gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user gp False ccLink welcomeSharedMsgId useRelays subRole publicMemberCount_ Nothing
void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing (Just epochStart)
let cd = maybe (CDChannelRcv gInfo Nothing) (CDGroupRcv gInfo Nothing) hostMember_
cInfo = GroupChat gInfo Nothing
@@ -2067,40 +2067,40 @@ processChatCommand vr nm = \case
_ -> Chat cInfo [] emptyChatStats
pure $ CRNewPreparedChat user $ AChat SCTGroup chat
APIChangePreparedContactUser contactId newUserId -> withUser $ \user -> do
- ct@Contact {preparedContact} <- withFastStore $ \db -> getContact db vr user contactId
+ ct@Contact {preparedContact} <- withFastStore $ \db -> getContact db cxt user contactId
when (isNothing preparedContact) $ throwCmdError "contact doesn't have link to connect"
when (isJust $ contactConn ct) $ throwCmdError "contact already has connection"
newUser <- privateGetUser newUserId
- ct' <- withFastStore $ \db -> updatePreparedContactUser db vr user ct newUser
+ ct' <- withFastStore $ \db -> updatePreparedContactUser db cxt user ct newUser
-- create changed feature items (new user may have different preferences)
lift $ createContactChangedFeatureItems user ct ct'
pure $ CRContactUserChanged user ct newUser ct'
APIChangePreparedGroupUser groupId newUserId -> withUser $ \user -> do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user groupId
when (isNothing $ preparedGroup gInfo) $ throwCmdError "group doesn't have link to connect"
hostMember_ <-
if useRelays' gInfo
then pure Nothing
else do
- hostMember <- withFastStore $ \db -> getHostMember db vr user groupId
+ hostMember <- withFastStore $ \db -> getHostMember db cxt user groupId
when (isJust $ memberConn hostMember) $ throwCmdError "host member already has connection"
pure $ Just hostMember
newUser <- privateGetUser newUserId
- gInfo' <- withFastStore $ \db -> updatePreparedGroupUser db vr user gInfo hostMember_ newUser
+ gInfo' <- withFastStore $ \db -> updatePreparedGroupUser db cxt user gInfo hostMember_ newUser
pure $ CRGroupUserChanged user gInfo newUser gInfo'
APIConnectPreparedContact contactId incognito msgContent_ -> withUser $ \user -> do
- ct@Contact {preparedContact} <- withFastStore $ \db -> getContact db vr user contactId
+ ct@Contact {preparedContact} <- withFastStore $ \db -> getContact db cxt user contactId
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) `catchAllErrors` \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
+ ct' <- withFastStore $ \db -> getContact db cxt user contactId
toView $ CEvtChatInfoUpdated user (AChatInfo SCTDirect $ DirectChat ct')
throwError e
-- get updated contact with connection
- ct' <- withFastStore $ \db -> getContact db vr user contactId
+ ct' <- withFastStore $ \db -> getContact db cxt user contactId
-- create changed feature items (connecting incognito sends default preferences, instead of user preferences)
lift . when incognito $ createContactChangedFeatureItems user ct ct'
forM_ msgContent_ $ \mc -> do
@@ -2119,13 +2119,13 @@ processChatCommand vr nm = \case
r <- connectViaContact user (Just $ PCEContact ct) incognito ccLink welcomeSharedMsgId msg_ `catchAllErrors` \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
+ ct' <- withFastStore $ \db -> getContact db cxt 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
+ ct' <- withFastStore $ \db -> getContact db cxt user contactId
-- create changed feature items (connecting incognito sends default preferences, instead of user preferences)
lift . when incognito $ createContactChangedFeatureItems user ct ct'
forM_ msg_ $ \(sharedMsgId, mc) -> do
@@ -2134,7 +2134,7 @@ processChatCommand vr nm = \case
pure $ CRStartedConnectionToContact user ct' customUserProfile
CVRConnectedContact ct' -> pure $ CRContactAlreadyExists user ct'
APIConnectPreparedGroup {groupId, incognito, ownerContact, msgContent_} -> withUser $ \user -> do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user groupId
case gInfo of
GroupInfo {preparedGroup = Nothing} -> throwCmdError "group doesn't have link to connect"
GroupInfo {useRelays = BoolDef True, preparedGroup = Just PreparedGroup {connLinkToConnect}} -> do
@@ -2157,14 +2157,14 @@ processChatCommand vr nm = \case
gVar <- asks random
(_, memberPrivKey) <- liftIO $ atomically $ C.generateKeyPair gVar
gInfo' <- withFastStore $ \db -> do
- gInfo' <- updatePreparedRelayedGroup db vr user gInfo mainCReq cReqHash incognitoProfile rootKey memberPrivKey publicMemberCount_
+ gInfo' <- updatePreparedRelayedGroup db cxt user gInfo mainCReq cReqHash incognitoProfile rootKey memberPrivKey publicMemberCount_
-- Pre-emptively create owner members with trusted keys from link data
forM_ owners $ \OwnerAuth {ownerId, ownerKey} -> do
let ctId_ = case ownerContact of
Just GroupOwnerContact {contactId, memberId}
| memberId == MemberId ownerId -> Just contactId
_ -> Nothing
- void $ createLinkOwnerMember db vr user gInfo' ctId_ (MemberId ownerId) ownerKey
+ void $ createLinkOwnerMember db cxt user gInfo' ctId_ (MemberId ownerId) ownerKey
pure gInfo'
rs <- withGroupLock "connectPreparedGroup" groupId $
mapConcurrently (connectToRelay user gInfo') relays
@@ -2182,7 +2182,7 @@ processChatCommand vr nm = \case
else do
gInfo'' <- withFastStore $ \db -> do
liftIO $ setPreparedGroupStartedConnection db groupId
- getGroupInfo db vr user groupId
+ getGroupInfo db cxt user groupId
-- Async retry failed relays with temporary errors
let retryable = [(l, m) | r@(l, m, _) <- failed, isTempErr r]
void $ mapConcurrently (uncurry $ retryRelayConnectionAsync gInfo') retryable
@@ -2202,7 +2202,7 @@ processChatCommand vr nm = \case
newConnIds <- getAgentConnShortLinkAsync user CFGetRelayDataJoin Nothing relayLink
withStore' $ \db -> createRelayMemberConnectionAsync db user gInfo' relayMember relayLink newConnIds subMode
GroupInfo {preparedGroup = Just PreparedGroup {connLinkToConnect, welcomeSharedMsgId, requestSharedMsgId}} -> do
- hostMember <- withFastStore $ \db -> getHostMember db vr user groupId
+ hostMember <- withFastStore $ \db -> getHostMember db cxt user groupId
msg_ <- forM msgContent_ $ \mc -> case requestSharedMsgId of
Just smId -> pure (smId, mc)
Nothing -> do
@@ -2212,7 +2212,7 @@ processChatCommand vr nm = \case
r <- connectViaContact user (Just $ PCEGroup gInfo hostMember) incognito connLinkToConnect welcomeSharedMsgId msg_ `catchAllErrors` \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
+ gInfo' <- withFastStore $ \db -> getGroupInfo db cxt user groupId
toView $ CEvtChatInfoUpdated user (AChatInfo SCTGroup $ GroupChat gInfo' Nothing)
throwError e
case r of
@@ -2220,7 +2220,7 @@ processChatCommand vr nm = \case
-- get updated group info (connLinkStartedConnection and incognito membership)
gInfo' <- withFastStore $ \db -> do
liftIO $ setPreparedGroupStartedConnection db groupId
- getGroupInfo db vr user groupId
+ getGroupInfo db cxt user groupId
forM_ msg_ $ \(sharedMsgId, mc) -> do
ci <- createChatItem user (CDGroupSnd gInfo' Nothing) False (CISndMsgContent mc) (Just sharedMsgId) Nothing
toView $ CEvtNewChatItems user [ci]
@@ -2250,7 +2250,7 @@ processChatCommand vr nm = \case
Connect _ Nothing -> throwChatError CEInvalidConnReq
APIVerifySimplexName chatRef -> withUser $ \user -> apiVerifySimplexName user chatRef
APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do
- ct@Contact {profile = LocalProfile {contactLink}} <- withFastStore $ \db -> getContact db vr user contactId
+ ct@Contact {profile = LocalProfile {contactLink}} <- withFastStore $ \db -> getContact db cxt user contactId
ccLink <- case contactLink of
Just (CLFull cReq) -> pure $ CCLink cReq Nothing
Just (CLShort sLnk) -> do
@@ -2260,7 +2260,7 @@ processChatCommand vr nm = \case
connectContactViaAddress user incognito ct ccLink `catchAllErrors` \e -> do
-- get updated contact, in case connection was started - in UI it would lock ability to change incognito choice
-- on next connection attempt, in case server received request while client got network error
- ct' <- withFastStore $ \db -> getContact db vr user contactId
+ ct' <- withFastStore $ \db -> getContact db cxt user contactId
toView $ CEvtChatInfoUpdated user (AChatInfo SCTDirect $ DirectChat ct')
throwError e
ConnectSimplex incognito -> withUser $ \user -> do
@@ -2269,9 +2269,9 @@ processChatCommand vr nm = \case
DeleteContact cName cdm -> withContactName cName $ \ctId -> APIDeleteChat (ChatRef CTDirect ctId Nothing) cdm
ClearContact cName -> withContactName cName $ \chatId -> APIClearChat $ ChatRef CTDirect chatId Nothing
APIListContacts userId -> withUserId userId $ \user ->
- CRContactsList user <$> withFastStore' (\db -> getUserContacts db vr user)
+ CRContactsList user <$> withFastStore' (\db -> getUserContacts db cxt user)
ListContacts -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APIListContacts userId
+ processChatCommand cxt nm $ APIListContacts userId
APICreateMyAddress userId -> withUserId userId $ \user@User {userChatRelay} -> do
withFastStore' (\db -> runExceptT $ getUserAddress db user) >>= \case
Left SEUserContactLinkNotFound -> pure ()
@@ -2289,9 +2289,9 @@ processChatCommand vr nm = \case
withFastStore $ \db -> createUserContactLink db user connId ccLink'' subMode
pure $ CRUserContactLinkCreated user ccLink''
CreateMyAddress -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APICreateMyAddress userId
+ processChatCommand cxt nm $ APICreateMyAddress userId
APIDeleteMyAddress userId -> withUserId userId $ \user@User {profile = p} -> do
- conn <- withFastStore $ \db -> getUserAddressConnection db vr user
+ conn <- withFastStore $ \db -> getUserAddressConnection db cxt user
withChatLock "deleteMyAddress" $ do
deleteAgentConnectionAsync $ aConnId conn
withFastStore' (`deleteUserAddress` user)
@@ -2302,11 +2302,11 @@ processChatCommand vr nm = \case
_ -> user
pure $ CRUserContactLinkDeleted user'
DeleteMyAddress -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APIDeleteMyAddress userId
+ processChatCommand cxt nm $ APIDeleteMyAddress userId
APIShowMyAddress userId -> withUserId' userId $ \user ->
CRUserContactLink user <$> withFastStore (`getUserAddress` user)
ShowMyAddress -> withUser' $ \User {userId} ->
- processChatCommand vr nm $ APIShowMyAddress userId
+ processChatCommand cxt nm $ APIShowMyAddress userId
APIAddMyAddressShortLink userId -> withUserId' userId $ \user ->
CRUserContactLink user <$> (withFastStore (`getUserAddress` user) >>= setMyAddressData user)
APISetProfileAddress userId False -> withUserId userId $ \user@User {profile = p} -> do
@@ -2318,7 +2318,7 @@ processChatCommand vr nm = \case
let p' = (fromLocalProfile p :: Profile) {contactLink = Just $ profileContactLink ucl}
updateProfile_ user p' True $ withFastStore' $ \db -> setUserProfileContactLink db user $ Just ucl
SetProfileAddress onOff -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APISetProfileAddress userId onOff
+ processChatCommand cxt nm $ APISetProfileAddress userId onOff
APISetAddressSettings userId settings@AddressSettings {businessAddress, autoAccept} -> withUserId userId $ \user -> do
ucl@UserContactLink {userContactLinkId, shortLinkDataSet, addressSettings} <- withFastStore (`getUserAddress` user)
forM_ autoAccept $ \AutoAccept {acceptIncognito} -> do
@@ -2332,43 +2332,43 @@ processChatCommand vr nm = \case
withFastStore' $ \db -> updateUserAddressSettings db userContactLinkId settings
pure $ CRUserContactLinkUpdated user ucl''
SetAddressSettings settings -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APISetAddressSettings userId settings
+ processChatCommand cxt nm $ APISetAddressSettings userId settings
AcceptContact incognito cName -> withUser $ \User {userId} -> do
connReqId <- withFastStore $ \db -> getContactRequestIdByName db userId cName
- processChatCommand vr nm $ APIAcceptContact incognito connReqId
+ processChatCommand cxt nm $ APIAcceptContact incognito connReqId
RejectContact cName -> withUser $ \User {userId} -> do
connReqId <- withFastStore $ \db -> getContactRequestIdByName db userId cName
- processChatCommand vr nm $ APIRejectContact connReqId
+ processChatCommand cxt nm $ APIRejectContact connReqId
ForwardMessage toChatName fromContactName forwardedMsg -> withUser $ \user -> do
contactId <- withFastStore $ \db -> getContactIdByName db user fromContactName
forwardedItemId <- withFastStore $ \db -> getDirectChatItemIdByText' db user contactId forwardedMsg
toChatRef <- getChatRef user toChatName
asGroup <- getSendAsGroup user toChatRef
- processChatCommand vr nm $ APIForwardChatItems toChatRef asGroup (ChatRef CTDirect contactId Nothing) (forwardedItemId :| []) Nothing
+ processChatCommand cxt nm $ APIForwardChatItems toChatRef asGroup (ChatRef CTDirect contactId Nothing) (forwardedItemId :| []) Nothing
ForwardGroupMessage toChatName fromGroupName fromMemberName_ forwardedMsg -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user fromGroupName
forwardedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId fromMemberName_ forwardedMsg
toChatRef <- getChatRef user toChatName
asGroup <- getSendAsGroup user toChatRef
- processChatCommand vr nm $ APIForwardChatItems toChatRef asGroup (ChatRef CTGroup groupId Nothing) (forwardedItemId :| []) Nothing
+ processChatCommand cxt nm $ APIForwardChatItems toChatRef asGroup (ChatRef CTGroup groupId Nothing) (forwardedItemId :| []) Nothing
ForwardLocalMessage toChatName forwardedMsg -> withUser $ \user -> do
folderId <- withFastStore (`getUserNoteFolderId` user)
forwardedItemId <- withFastStore $ \db -> getLocalChatItemIdByText' db user folderId forwardedMsg
toChatRef <- getChatRef user toChatName
asGroup <- getSendAsGroup user toChatRef
- processChatCommand vr nm $ APIForwardChatItems toChatRef asGroup (ChatRef CTLocal folderId Nothing) (forwardedItemId :| []) Nothing
+ processChatCommand cxt nm $ APIForwardChatItems toChatRef asGroup (ChatRef CTLocal folderId Nothing) (forwardedItemId :| []) Nothing
SharePublicGroup shareGroupName toChatName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user shareGroupName
toChatRef <- getChatRef user toChatName
sendRef <- case toChatRef of
ChatRef CTDirect ctId _ -> pure $ SRDirect ctId
ChatRef CTGroup gId scope_ -> do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user gId
pure $ SRGroup gId scope_ (useRelays' gInfo)
_ -> throwCmdError "unsupported share target"
- processChatCommand vr nm (APIShareChatMsgContent (ChatRef CTGroup groupId Nothing) sendRef) >>= \case
+ processChatCommand cxt nm (APIShareChatMsgContent (ChatRef CTGroup groupId Nothing) sendRef) >>= \case
CRChatMsgContent _ mc ->
- processChatCommand vr nm $ APISendMessages sendRef False Nothing [composedMessage Nothing mc]
+ processChatCommand cxt nm $ APISendMessages sendRef False Nothing [composedMessage Nothing mc]
r -> pure r
SendMessage sendName msg -> withUser $ \user -> do
let mc = MCText msg
@@ -2377,57 +2377,57 @@ processChatCommand vr nm = \case
withFastStore' (\db -> runExceptT $ getContactIdByName db user name) >>= \case
Right ctId -> do
let sendRef = SRDirect ctId
- processChatCommand vr nm $ APISendMessages sendRef False Nothing [composedMessage Nothing mc]
+ processChatCommand cxt nm $ APISendMessages sendRef False Nothing [composedMessage Nothing mc]
Left _ ->
- withFastStore' (\db -> runExceptT $ getActiveMembersByName db vr user name) >>= \case
+ withFastStore' (\db -> runExceptT $ getActiveMembersByName db cxt user name) >>= \case
Right [(gInfo, member)] -> do
let GroupInfo {localDisplayName = gName} = gInfo
GroupMember {localDisplayName = mName} = member
- processChatCommand vr nm $ SendMemberContactMessage gName mName msg
+ processChatCommand cxt nm $ SendMemberContactMessage gName mName msg
Right (suspectedMember : _) ->
throwChatError $ CEContactNotFound name (Just suspectedMember)
_ ->
throwChatError $ CEContactNotFound name Nothing
SNGroup name scope_ -> do
(gInfo, cScope_, mentions) <- withFastStore $ \db -> do
- gInfo <- getGroupInfoByName db vr user name
+ gInfo <- getGroupInfoByName db cxt user name
let gId = groupId' gInfo
cScope_ <-
forM scope_ $ \(GSNMemberSupport mName_) ->
GCSMemberSupport <$> mapM (getGroupMemberIdByName db user gId) mName_
(gInfo, cScope_,) <$> liftIO (getMessageMentions db user gId msg)
let sendRef = SRGroup (groupId' gInfo) cScope_ (sendAsGroup' gInfo cScope_)
- processChatCommand vr nm $ APISendMessages sendRef False Nothing [ComposedMessage Nothing Nothing mc mentions]
+ processChatCommand cxt nm $ APISendMessages sendRef False Nothing [ComposedMessage Nothing Nothing mc mentions]
SNLocal -> do
folderId <- withFastStore (`getUserNoteFolderId` user)
- processChatCommand vr nm $ APICreateChatItems folderId [composedMessage Nothing mc]
+ processChatCommand cxt nm $ APICreateChatItems folderId [composedMessage Nothing mc]
SendMemberContactMessage gName mName msg -> withUser $ \user -> do
(gId, mId) <- getGroupAndMemberId user gName mName
- m <- withFastStore $ \db -> getGroupMember db vr user gId mId
+ m <- withFastStore $ \db -> getGroupMember db cxt user gId mId
let mc = MCText msg
case memberContactId m of
Nothing -> do
- g <- withFastStore $ \db -> getGroupInfo db vr user gId
+ g <- withFastStore $ \db -> getGroupInfo db cxt user gId
unless (groupFeatureUserAllowed SGFDirectMessages g) $ throwCmdError "direct messages not allowed"
toView $ CEvtNoMemberContactCreating user g m
- processChatCommand vr nm (APICreateMemberContact gId mId) >>= \case
+ processChatCommand cxt nm (APICreateMemberContact gId mId) >>= \case
CRNewMemberContact _ ct@Contact {contactId} _ _ -> do
toViewTE $ TENewMemberContact user ct g m
- processChatCommand vr nm $ APISendMemberContactInvitation contactId (Just mc)
+ processChatCommand cxt nm $ APISendMemberContactInvitation contactId (Just mc)
cr -> pure cr
Just ctId -> do
let sendRef = SRDirect ctId
- processChatCommand vr nm $ APISendMessages sendRef False Nothing [composedMessage Nothing mc]
+ processChatCommand cxt nm $ APISendMessages sendRef False Nothing [composedMessage Nothing mc]
AcceptMemberContact cName -> withUser $ \user -> do
contactId <- withFastStore $ \db -> getContactIdByName db user cName
- processChatCommand vr nm $ APIAcceptMemberContact contactId
+ processChatCommand cxt nm $ APIAcceptMemberContact contactId
SendLiveMessage chatName msg -> withUser $ \user -> do
(chatRef, mentions) <- getChatRefAndMentions user chatName msg
withSendRef user chatRef $ \sendRef -> do
let mc = MCText msg
- processChatCommand vr nm $ APISendMessages sendRef True Nothing [ComposedMessage Nothing Nothing mc mentions]
+ processChatCommand cxt nm $ APISendMessages sendRef True Nothing [ComposedMessage Nothing Nothing mc mentions]
SendMessageBroadcast mc -> withUser $ \user -> do
- contacts <- withFastStore' $ \db -> getUserContacts db vr user
+ contacts <- withFastStore' $ \db -> getUserContacts db cxt user
withChatLock "sendMessageBroadcast" $ do
let ctConns_ = L.nonEmpty $ foldr addContactConn [] contacts
case ctConns_ of
@@ -2470,28 +2470,28 @@ processChatCommand vr nm = \case
contactId <- withFastStore $ \db -> getContactIdByName db user cName
quotedItemId <- withFastStore $ \db -> getDirectChatItemIdByText db userId contactId msgDir quotedMsg
let mc = MCText msg
- processChatCommand vr nm $ APISendMessages (SRDirect contactId) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc M.empty]
+ processChatCommand cxt nm $ APISendMessages (SRDirect contactId) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc M.empty]
DeleteMessage chatName deletedMsg -> withUser $ \user -> do
chatRef <- getChatRef user chatName
deletedItemId <- getSentChatItemIdByText user chatRef deletedMsg
- processChatCommand vr nm $ APIDeleteChatItem chatRef (deletedItemId :| []) CIDMBroadcast
+ processChatCommand cxt nm $ APIDeleteChatItem chatRef (deletedItemId :| []) CIDMBroadcast
DeleteMemberMessage gName mName deletedMsg -> withUser $ \user -> do
gId <- withFastStore $ \db -> getGroupIdByName db user gName
deletedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user gId (Just mName) deletedMsg
- processChatCommand vr nm $ APIDeleteMemberChatItem gId (deletedItemId :| [])
+ processChatCommand cxt nm $ APIDeleteMemberChatItem gId (deletedItemId :| [])
EditMessage chatName editedMsg msg -> withUser $ \user -> do
(chatRef, mentions) <- getChatRefAndMentions user chatName msg
editedItemId <- getSentChatItemIdByText user chatRef editedMsg
let mc = MCText msg
- processChatCommand vr nm $ APIUpdateChatItem chatRef editedItemId False $ UpdatedMessage mc mentions
+ processChatCommand cxt nm $ APIUpdateChatItem chatRef editedItemId False $ UpdatedMessage mc mentions
UpdateLiveMessage chatName chatItemId live msg -> withUser $ \user -> do
(chatRef, mentions) <- getChatRefAndMentions user chatName msg
let mc = MCText msg
- processChatCommand vr nm $ APIUpdateChatItem chatRef chatItemId live $ UpdatedMessage mc mentions
+ processChatCommand cxt nm $ APIUpdateChatItem chatRef chatItemId live $ UpdatedMessage mc mentions
ReactToMessage add reaction chatName msg -> withUser $ \user -> do
chatRef <- getChatRef user chatName
chatItemId <- getChatItemIdByText user chatRef msg
- processChatCommand vr nm $ APIChatItemReaction chatRef chatItemId add reaction
+ processChatCommand cxt nm $ APIChatItemReaction chatRef chatItemId add reaction
APINewGroup userId incognito gProfile -> withUserId userId $ \user -> do
g <- asks random
memberId <- liftIO $ MemberId <$> encodedRandomBytes g 12
@@ -2499,7 +2499,7 @@ processChatCommand vr nm = \case
createNewGroupItems user gInfo
pure $ CRGroupCreated user gInfo
NewGroup incognito gProfile -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APINewGroup userId incognito gProfile
+ processChatCommand cxt nm $ APINewGroup userId incognito gProfile
APINewPublicGroup userId incognito relayIds groupProfile -> withUserId userId $ \user -> do
(gProfile', memberId, groupKeys, setupLink) <- prepareGroupLink user
gInfo <- newGroup user incognito gProfile' True memberId (Just groupKeys) (Just 1)
@@ -2559,16 +2559,16 @@ processChatCommand vr nm = \case
pure (gLink, results)
pure (groupProfile', memberId, groupKeys, setupLink)
NewPublicGroup incognito relayIds gProfile -> withUser $ \User {userId} ->
- processChatCommand vr nm $ APINewPublicGroup userId incognito relayIds gProfile
+ processChatCommand cxt nm $ APINewPublicGroup userId incognito relayIds gProfile
APIGetGroupRelays groupId -> withUser $ \user -> do
(gInfo, relays) <- withFastStore $ \db -> do
- gInfo <- getGroupInfo db vr user groupId
+ gInfo <- getGroupInfo db cxt user groupId
relays <- liftIO $ getGroupRelays db gInfo
pure (gInfo, relays)
pure $ CRGroupRelays user gInfo relays
APIAddGroupRelays groupId relayIds -> withUser $ \user -> withGroupLock "addGroupRelays" groupId $ do
(gInfo, existingRelays) <- withFastStore $ \db -> do
- gi <- getGroupInfo db vr user groupId
+ gi <- getGroupInfo db cxt user groupId
rs <- liftIO $ getGroupRelays db gi
pure (gi, rs)
assertUserGroupRole gInfo GROwner
@@ -2599,7 +2599,7 @@ processChatCommand vr nm = \case
_ -> False
APIAddMember groupId contactId memRole -> withUser $ \user -> withGroupLock "addMember" groupId $ do
-- TODO for large groups: no need to load all members to determine if contact is a member
- (group, contact) <- withFastStore $ \db -> (,) <$> getGroup db vr user groupId <*> getContact db vr user contactId
+ (group, contact) <- withFastStore $ \db -> (,) <$> getGroup db cxt user groupId <*> getContact db cxt user contactId
let Group gInfo members = group
Contact {localDisplayName = cName} = contact
when (useRelays' gInfo) $ throwCmdError "can't invite contact to channel"
@@ -2630,8 +2630,8 @@ processChatCommand vr nm = \case
APIJoinGroup groupId enableNtfs -> withUser $ \user@User {userId} -> do
withGroupLock "joinGroup" groupId $ do
(invitation, ct) <- withFastStore $ \db -> do
- inv@ReceivedGroupInvitation {fromMember} <- getGroupInvitation db vr user groupId
- (inv,) <$> getContactViaMember db vr user fromMember
+ inv@ReceivedGroupInvitation {fromMember} <- getGroupInvitation db cxt user groupId
+ (inv,) <$> getContactViaMember db cxt user fromMember
let ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership, chatSettings}} = invitation
GroupMember {memberId = membershipMemId} = membership
Contact {activeConn} = ct
@@ -2642,7 +2642,7 @@ processChatCommand vr nm = \case
agentConnId <- case memberConn fromMember of
Nothing -> do
agentConnId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff
- let chatV = vr `peerConnChatVersion` peerChatVRange
+ let chatV = vr cxt `peerConnChatVersion` peerChatVRange
void $ withFastStore' $ \db -> createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode
pure agentConnId
Just conn -> pure $ aConnId conn
@@ -2661,7 +2661,7 @@ processChatCommand vr nm = \case
pure $ CRUserAcceptedGroupSent user g {membership = membership {memberStatus = GSMemAccepted}} Nothing
Nothing -> throwChatError $ CEContactNotActive ct
APIAcceptMember groupId gmId role -> withUser $ \user@User {userId} -> do
- (gInfo, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user gmId
+ (gInfo, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db cxt user groupId <*> getGroupMemberById db cxt user gmId
assertUserGroupRole gInfo $ max GRModerator role
case memberStatus m of
GSMemPendingApproval | memberCategory m == GCInviteeMember -> do -- only host can approve
@@ -2670,14 +2670,14 @@ processChatCommand vr nm = \case
Just mConn ->
case memberAdmission >>= review of
Just MCAll -> do
- introduceToModerators vr user gInfo m
+ introduceToModerators cxt user gInfo m
withFastStore' $ \db -> updateGroupMemberStatus db userId m GSMemPendingReview
let m' = m {memberStatus = GSMemPendingReview}
pure $ CRMemberAccepted user gInfo m'
Nothing -> do
let msg = XGrpLinkAcpt GAAccepted role (memberId' m)
void $ sendDirectMemberMessage mConn msg groupId
- introduceToRemaining vr user gInfo m {memberRole = role}
+ introduceToRemaining cxt user gInfo m {memberRole = role}
when (groupFeatureAllowed SGFHistory gInfo) $ sendHistory user gInfo m
(m', gInfo') <- withFastStore' $ \db -> do
m' <- updateGroupMemberAccepted db user m GSMemConnected role
@@ -2692,7 +2692,7 @@ processChatCommand vr nm = \case
Nothing -> throwChatError CEGroupMemberNotActive
GSMemPendingReview -> do
let scope = Just $ GCSMemberSupport $ Just (groupMemberId' m)
- modMs <- withFastStore' $ \db -> getGroupModerators db vr user gInfo
+ modMs <- withFastStore' $ \db -> getGroupModerators db cxt user gInfo
let rcpModMs' = filter memberCurrent modMs
msg = XGrpLinkAcpt GAAccepted role (memberId' m)
void $ sendGroupMessage user gInfo scope ([m] <> rcpModMs') msg
@@ -2701,7 +2701,7 @@ processChatCommand vr nm = \case
let msg2 = XMsgNew $ mcSimple (MCText acceptedToGroupMessage)
void $ sendDirectMemberMessage mConn msg2 groupId
when (memberCategory m == GCInviteeMember) $ do
- introduceToRemaining vr user gInfo m {memberRole = role}
+ introduceToRemaining cxt user gInfo m {memberRole = role}
when (groupFeatureAllowed SGFHistory gInfo) $ sendHistory user gInfo m
(m', gInfo') <- withFastStore' $ \db -> do
m' <- updateGroupMemberAccepted db user m newMemberStatus role
@@ -2719,7 +2719,7 @@ processChatCommand vr nm = \case
_ -> GSMemAnnounced
_ -> throwCmdError "member should be pending approval and invitee, or pending review and not invitee"
APIDeleteMemberSupportChat groupId gmId -> withUser $ \user -> do
- (gInfo, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user gmId
+ (gInfo, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db cxt user groupId <*> getGroupMemberById db cxt user gmId
when (isNothing $ supportChat m) $ throwCmdError "member has no support chat"
when (memberPending m) $ throwCmdError "member is pending"
(gInfo', m') <- withFastStore' $ \db -> do
@@ -2733,7 +2733,7 @@ processChatCommand vr nm = \case
APIMembersRole groupId memberIds newRole -> withUser $ \user ->
withGroupLock "memberRole" groupId $ do
-- TODO [relays] possible optimization is to read only required members + relays
- g@(Group gInfo members) <- withFastStore $ \db -> getGroup db vr user groupId
+ g@(Group gInfo members) <- withFastStore $ \db -> getGroup db cxt user groupId
when (selfSelected gInfo) $ throwCmdError "can't change role for self"
let (invitedMems, currentMems, unchangedMems, maxRole, anyAdmin, anyPending) = selectMembers members
when (length invitedMems + length currentMems + length unchangedMems /= length memberIds) $ throwChatError CEGroupMemberNotFound
@@ -2771,7 +2771,7 @@ processChatCommand vr nm = \case
where
changeRole :: GroupMember -> CM GroupMember
changeRole m@GroupMember {groupMemberId, memberContactId, localDisplayName = cName} = do
- withFastStore (\db -> (,) <$> mapM (getContact db vr user) memberContactId <*> liftIO (getMemberInvitation db user groupMemberId)) >>= \case
+ withFastStore (\db -> (,) <$> mapM (getContact db cxt user) memberContactId <*> liftIO (getMemberInvitation db user groupMemberId)) >>= \case
(Just ct, Just cReq) -> do
sendGrpInvitation user ct gInfo (m :: GroupMember) {memberRole = newRole} cReq
withFastStore' $ \db -> updateGroupMemberRole db user m newRole
@@ -2803,7 +2803,7 @@ processChatCommand vr nm = \case
APIBlockMembersForAll groupId memberIds blockFlag -> withUser $ \user ->
withGroupLock "blockForAll" groupId $ do
-- TODO [relays] possible optimization is to read only required members + relays
- Group gInfo members <- withFastStore $ \db -> getGroup db vr user groupId
+ Group gInfo members <- withFastStore $ \db -> getGroup db cxt user groupId
when (selfSelected gInfo) $ throwCmdError "can't block/unblock self"
-- TODO [relays] consider sending restriction to all members (remove filtering), as we do in delivery jobs
let (blockMems, remainingMems, maxRole, anyAdmin, anyPending) = selectMembers members
@@ -2852,7 +2852,7 @@ processChatCommand vr nm = \case
APIRemoveMembers {groupId, groupMemberIds, withMessages} -> withUser $ \user ->
withGroupLock "removeMembers" groupId $ do
-- TODO [relays] possible optimization is to read only required members + relays
- Group gInfo members <- withFastStore $ \db -> getGroup db vr user groupId
+ Group gInfo members <- withFastStore $ \db -> getGroup db cxt user groupId
let (count, invitedMems, pendingApprvMems, pendingRvwMems, currentMems, maxRole, anyAdmin) = selectMembers gmIds members
gmIds = S.fromList $ L.toList groupMemberIds
memCount = length groupMemberIds
@@ -2875,7 +2875,7 @@ processChatCommand vr nm = \case
gInfo' <-
if useRelays' gInfo
then updatePublicGroupData user gInfo
- else withFastStore $ \db -> getGroupInfo db vr user groupId
+ else withFastStore $ \db -> getGroupInfo db cxt user groupId
let acis' = map (updateACIGroupInfo gInfo') acis
unless (null acis') $ toView $ CEvtNewChatItems user acis'
unless (null errs) $ toView $ CEvtChatErrors errs
@@ -2951,7 +2951,7 @@ processChatCommand vr nm = \case
| groupFeatureUserAllowed SGFFullDelete gInfo = deleteGroupMembersCIs user gInfo ms
| otherwise = markGroupMembersCIsDeleted user gInfo ms membership
APILeaveGroup groupId -> withUser $ \user@User {userId} -> do
- gInfo@GroupInfo {membership} <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ gInfo@GroupInfo {membership} <- withFastStore $ \db -> getGroupInfo db cxt user groupId
filesInfo <- withFastStore' $ \db -> getGroupFileInfo db user gInfo
withGroupLock "leaveGroup" groupId $ do
cancelFilesInProgress user filesInfo
@@ -2990,26 +2990,26 @@ processChatCommand vr nm = \case
pure msg
getRecipients user gInfo
| useRelays' gInfo = do
- relays <- withFastStore' $ \db -> getGroupRelayMembers db vr user gInfo
+ relays <- withFastStore' $ \db -> getGroupRelayMembers db cxt user gInfo
pure (relays, relays)
| otherwise = do
- ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
+ ms <- withFastStore' $ \db -> getGroupMembers db cxt user gInfo
pure (ms, filter memberCurrentOrPending ms)
APIListMembers groupId -> withUser $ \user ->
- CRGroupMembers user <$> withFastStore (\db -> getGroup db vr user groupId)
+ CRGroupMembers user <$> withFastStore (\db -> getGroup db cxt user groupId)
-- -- validate: prohibit to delete/archive if member is pending (has to communicate approval or rejection)
-- APIDeleteGroupConversations groupId _gcId -> withUser $ \user -> do
- -- _gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ -- _gInfo <- withFastStore $ \db -> getGroupInfo db cxt user groupId
-- ok_ -- CRGroupConversationsArchived
-- APIArchiveGroupConversations groupId _gcId -> withUser $ \user -> do
- -- _gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ -- _gInfo <- withFastStore $ \db -> getGroupInfo db cxt user groupId
-- ok_ -- CRGroupConversationsDeleted
AddMember gName cName memRole -> withUser $ \user -> do
(groupId, contactId) <- withFastStore $ \db -> (,) <$> getGroupIdByName db user gName <*> getContactIdByName db user cName
- processChatCommand vr nm $ APIAddMember groupId contactId memRole
+ processChatCommand cxt nm $ APIAddMember groupId contactId memRole
JoinGroup gName enableNtfs -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APIJoinGroup groupId enableNtfs
+ processChatCommand cxt nm $ APIJoinGroup groupId enableNtfs
AcceptMember gName gMemberName memRole -> withMemberName gName gMemberName $ \gId gMemberId -> APIAcceptMember gId gMemberId memRole
MemberRole gName gMemberName memRole -> withMemberName gName gMemberName $ \gId gMemberId -> APIMembersRole gId [gMemberId] memRole
BlockForAll gName gMemberName blocked -> withMemberName gName gMemberName $ \gId gMemberId -> APIBlockMembersForAll gId [gMemberId] blocked
@@ -3018,45 +3018,45 @@ processChatCommand vr nm = \case
gId <- getGroupIdByName db user gName
gMemberIds <- mapM (getGroupMemberIdByName db user gId) gMemberNames
pure (gId, gMemberIds)
- processChatCommand vr nm $ APIRemoveMembers gId gMemberIds withMessages
+ processChatCommand cxt nm $ APIRemoveMembers gId gMemberIds withMessages
LeaveGroup gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APILeaveGroup groupId
+ processChatCommand cxt nm $ APILeaveGroup groupId
AllowRelayGroup gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APIAllowRelayGroup groupId
+ processChatCommand cxt nm $ APIAllowRelayGroup groupId
DeleteGroup gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APIDeleteChat (ChatRef CTGroup groupId Nothing) (CDMFull True)
+ processChatCommand cxt nm $ APIDeleteChat (ChatRef CTGroup groupId Nothing) (CDMFull True)
ClearGroup gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APIClearChat (ChatRef CTGroup groupId Nothing)
+ processChatCommand cxt nm $ APIClearChat (ChatRef CTGroup groupId Nothing)
ListMembers gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APIListMembers groupId
+ processChatCommand cxt nm $ APIListMembers groupId
ListMemberSupportChats gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- (Group gInfo members) <- withFastStore $ \db -> getGroup db vr user groupId
+ (Group gInfo members) <- withFastStore $ \db -> getGroup db cxt user groupId
let memberSupportChats = filter (isJust . supportChat) members
pure $ CRMemberSupportChats user gInfo memberSupportChats
APIListGroups userId contactId_ search_ -> withUserId userId $ \user ->
- CRGroupsList user <$> withFastStore' (\db -> getBaseGroupDetails db vr user contactId_ search_)
+ CRGroupsList user <$> withFastStore' (\db -> getBaseGroupDetails db cxt user contactId_ search_)
ListGroups cName_ search_ -> withUser $ \user@User {userId} -> do
- ct_ <- forM cName_ $ \cName -> withFastStore $ \db -> getContactByName db vr user cName
- processChatCommand vr nm $ APIListGroups userId (contactId' <$> ct_) search_
+ ct_ <- forM cName_ $ \cName -> withFastStore $ \db -> getContactByName db cxt user cName
+ processChatCommand cxt nm $ APIListGroups userId (contactId' <$> ct_) search_
APIUpdateGroupProfile groupId p' -> withUser $ \user -> do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user groupId
runUpdateGroupProfile user gInfo p'
UpdateGroupNames gName GroupProfile {displayName, fullName, shortDescr} ->
updateGroupProfileByName gName $ \p -> p {displayName, fullName, shortDescr}
ShowGroupProfile gName -> withUser $ \user ->
- CRGroupProfile user <$> withFastStore (\db -> getGroupInfoByName db vr user gName)
+ CRGroupProfile user <$> withFastStore (\db -> getGroupInfoByName db cxt user gName)
UpdateGroupDescription gName description ->
updateGroupProfileByName gName $ \p -> p {description}
ShowGroupDescription gName -> withUser $ \user ->
- CRGroupDescription user <$> withFastStore (\db -> getGroupInfoByName db vr user gName)
+ CRGroupDescription user <$> withFastStore (\db -> getGroupInfoByName db cxt user gName)
APICreateGroupLink groupId mRole -> withUser $ \user -> withGroupLock "createGroupLink" groupId $ do
- gInfo@GroupInfo {groupProfile} <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ gInfo@GroupInfo {groupProfile} <- withFastStore $ \db -> getGroupInfo db cxt user groupId
assertUserGroupRole gInfo GRAdmin
when (mRole > GRMember) $ throwChatError $ CEGroupMemberInitialRole gInfo mRole
groupLinkId <- GroupLinkId <$> drgRandomBytes 16
@@ -3070,7 +3070,7 @@ processChatCommand vr nm = \case
gLink <- withFastStore $ \db -> createGroupLink db gVar user gInfo connId ccLink' groupLinkId mRole subMode
pure $ CRGroupLinkCreated user gInfo gLink
APIGroupLinkMemberRole groupId mRole' -> withUser $ \user -> withGroupLock "groupLinkMemberRole" groupId $ do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user groupId
gLnk@GroupLink {acceptMemberRole} <- withFastStore $ \db -> getGroupLink db user gInfo
assertUserGroupRole gInfo GRAdmin
when (mRole' > GRMember) $ throwChatError $ CEGroupMemberInitialRole gInfo mRole'
@@ -3080,22 +3080,22 @@ processChatCommand vr nm = \case
else pure gLnk
pure $ CRGroupLink user gInfo gLnk'
APIDeleteGroupLink groupId -> withUser $ \user -> withGroupLock "deleteGroupLink" groupId $ do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user groupId
deleteGroupLink' user gInfo
pure $ CRGroupLinkDeleted user gInfo
APIGetGroupLink groupId -> withUser $ \user -> do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user groupId
gLnk <- withFastStore $ \db -> getGroupLink db user gInfo
pure $ CRGroupLink user gInfo gLnk
APIAddGroupShortLink groupId -> withUser $ \user -> do
(gInfo, gLink) <- withFastStore $ \db -> do
- gInfo <- getGroupInfo db vr user groupId
+ gInfo <- getGroupInfo db cxt user groupId
gLink <- getGroupLink db user gInfo
pure (gInfo, gLink)
gLink' <- setGroupLinkData nm user gInfo gLink
pure $ CRGroupLink user gInfo gLink'
APICreateMemberContact gId gMemberId -> withUser $ \user -> do
- (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
+ (g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db cxt user gId <*> getGroupMember db cxt user gId gMemberId
assertUserGroupRole g GRAuthor
unless (groupFeatureUserAllowed SGFDirectMessages g) $ throwCmdError "direct messages not allowed"
case memberConn m of
@@ -3112,7 +3112,7 @@ processChatCommand vr nm = \case
pure $ CRNewMemberContact user ct g m
_ -> throwChatError CEGroupMemberNotActive
APISendMemberContactInvitation contactId msgContent_ -> withUser $ \user -> do
- (g@GroupInfo {groupId}, m, ct, cReq) <- withFastStore $ \db -> getMemberContact db vr user contactId
+ (g@GroupInfo {groupId}, m, ct, cReq) <- withFastStore $ \db -> getMemberContact db cxt user contactId
when (contactGrpInvSent ct) $ throwCmdError "x.grp.direct.inv already sent"
case memberConn m of
Just mConn -> do
@@ -3127,17 +3127,17 @@ processChatCommand vr nm = \case
pure $ CRNewMemberContactSentInv user ct' g m
_ -> throwChatError CEGroupMemberNotActive
APIAcceptMemberContact contactId -> withUser $ \user -> do
- (g, mConn, ct, groupDirectInv) <- withFastStore $ \db -> getMemberContactInvited db vr user contactId
+ (g, mConn, ct, groupDirectInv) <- withFastStore $ \db -> getMemberContactInvited db cxt user contactId
when (groupDirectInvStartedConnection groupDirectInv) $ throwCmdError "connection already started"
connectMemberContact user g mConn ct groupDirectInv `catchAllErrors` \e -> do
-- get updated contact, in case connection was started
- ct' <- withFastStore $ \db -> getContact db vr user contactId
+ ct' <- withFastStore $ \db -> getContact db cxt user contactId
toView $ CEvtChatInfoUpdated user (AChatInfo SCTDirect $ DirectChat ct')
throwError e
-- get updated contact (groupDirectInvStartedConnection) with connection
ct' <- withFastStore $ \db -> do
liftIO $ setMemberContactStartedConnection db ct
- getContact db vr user contactId
+ getContact db cxt user contactId
pure $ CRMemberContactAccepted user ct'
where
connectMemberContact user gInfo mConn Contact {activeConn} GroupDirectInvitation {groupDirectInvLink = cReq} =
@@ -3159,7 +3159,7 @@ processChatCommand vr nm = \case
acId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff
conn <- withStore $ \db -> do
connId <- liftIO $ createMemberContactConn db user acId Nothing gInfo mConn ConnPrepared contactId subMode
- getConnectionById db vr user connId
+ getConnectionById db cxt user connId
joinPreparedConn subMode conn
joinPreparedConn subMode conn = do
-- [incognito] send membership incognito profile
@@ -3170,66 +3170,66 @@ processChatCommand vr nm = \case
void $ withFastStore' $ \db -> updateConnectionStatusFromTo db conn ConnPrepared newStatus
CreateGroupLink gName mRole -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APICreateGroupLink groupId mRole
+ processChatCommand cxt nm $ APICreateGroupLink groupId mRole
GroupLinkMemberRole gName mRole -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APIGroupLinkMemberRole groupId mRole
+ processChatCommand cxt nm $ APIGroupLinkMemberRole groupId mRole
DeleteGroupLink gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APIDeleteGroupLink groupId
+ processChatCommand cxt nm $ APIDeleteGroupLink groupId
ShowGroupLink gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand vr nm $ APIGetGroupLink groupId
+ processChatCommand cxt nm $ APIGetGroupLink groupId
SendGroupMessageQuote gName cName quotedMsg msg -> withUser $ \user -> do
(gInfo, quotedItemId, mentions) <-
withFastStore $ \db -> do
- gInfo <- getGroupInfoByName db vr user gName
+ gInfo <- getGroupInfoByName db cxt user gName
let gId = groupId' gInfo
qiId <- getGroupChatItemIdByText db user gId cName quotedMsg
(gInfo, qiId,) <$> liftIO (getMessageMentions db user gId msg)
let mc = MCText msg
- processChatCommand vr nm $ APISendMessages (SRGroup (groupId' gInfo) Nothing (sendAsGroup' gInfo Nothing)) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc mentions]
+ processChatCommand cxt nm $ APISendMessages (SRGroup (groupId' gInfo) Nothing (sendAsGroup' gInfo Nothing)) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc mentions]
ClearNoteFolder -> withUser $ \user -> do
folderId <- withFastStore (`getUserNoteFolderId` user)
- processChatCommand vr nm $ APIClearChat (ChatRef CTLocal folderId Nothing)
+ processChatCommand cxt nm $ APIClearChat (ChatRef CTLocal folderId Nothing)
LastChats count_ -> withUser' $ \user -> do
let count = fromMaybe 5000 count_
- (errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db vr user False (PTLast count) clqNoFilters)
+ (errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db cxt user False (PTLast count) clqNoFilters)
unless (null errs) $ toView $ CEvtChatErrors (map ChatErrorStore errs)
pure $ CRChats previews
LastMessages (Just chatName) count search -> withUser $ \user -> do
chatRef <- getChatRef user chatName
- chatResp <- processChatCommand vr nm $ APIGetChat chatRef Nothing (CPLast count) search
+ chatResp <- processChatCommand cxt nm $ APIGetChat chatRef Nothing (CPLast count) search
pure $ CRChatItems user (Just chatName) (aChatItems . chat $ chatResp)
LastMessages Nothing count search -> withUser $ \user -> do
- chatItems <- withFastStore $ \db -> getAllChatItems db vr user (CPLast count) search
+ chatItems <- withFastStore $ \db -> getAllChatItems db cxt user (CPLast count) search
pure $ CRChatItems user Nothing chatItems
LastChatItemId (Just chatName) index -> withUser $ \user -> do
chatRef <- getChatRef user chatName
- chatResp <- processChatCommand vr nm $ APIGetChat chatRef Nothing (CPLast $ index + 1) Nothing
+ chatResp <- processChatCommand cxt nm $ APIGetChat chatRef Nothing (CPLast $ index + 1) Nothing
pure $ CRChatItemId user (fmap aChatItemId . listToMaybe . aChatItems . chat $ chatResp)
LastChatItemId Nothing index -> withUser $ \user -> do
- chatItems <- withFastStore $ \db -> getAllChatItems db vr user (CPLast $ index + 1) Nothing
+ chatItems <- withFastStore $ \db -> getAllChatItems db cxt user (CPLast $ index + 1) Nothing
pure $ CRChatItemId user (fmap aChatItemId . listToMaybe $ chatItems)
ShowChatItem (Just itemId) -> withUser $ \user -> do
chatItem <- withFastStore $ \db -> do
chatRef <- getChatRefViaItemId db user itemId
- getAChatItem db vr user chatRef itemId
+ getAChatItem db cxt user chatRef itemId
pure $ CRChatItems user Nothing ((: []) chatItem)
ShowChatItem Nothing -> withUser $ \user -> do
- chatItems <- withFastStore $ \db -> getAllChatItems db vr user (CPLast 1) Nothing
+ chatItems <- withFastStore $ \db -> getAllChatItems db cxt user (CPLast 1) Nothing
pure $ CRChatItems user Nothing chatItems
ShowChatItemInfo chatName msg -> withUser $ \user -> do
chatRef <- getChatRef user chatName
itemId <- getChatItemIdByText user chatRef msg
- processChatCommand vr nm $ APIGetChatItemInfo chatRef itemId
+ processChatCommand cxt nm $ APIGetChatItemInfo chatRef itemId
ShowLiveItems on -> withUser $ \_ ->
asks showLiveItems >>= atomically . (`writeTVar` on) >> ok_
SendFile chatName f -> withUser $ \user -> do
chatRef <- getChatRef user chatName
case chatRef of
- ChatRef CTLocal folderId _ -> processChatCommand vr nm $ APICreateChatItems folderId [composedMessage (Just f) (MCFile "")]
- _ -> withSendRef user chatRef $ \sendRef -> processChatCommand vr nm $ APISendMessages sendRef False Nothing [composedMessage (Just f) (MCFile "")]
+ ChatRef CTLocal folderId _ -> processChatCommand cxt nm $ APICreateChatItems folderId [composedMessage (Just f) (MCFile "")]
+ _ -> withSendRef user chatRef $ \sendRef -> processChatCommand cxt nm $ APISendMessages sendRef False Nothing [composedMessage (Just f) (MCFile "")]
SendImage chatName f@(CryptoFile fPath _) -> withUser $ \user -> do
chatRef <- getChatRef user chatName
withSendRef user chatRef $ \sendRef -> do
@@ -3238,7 +3238,7 @@ processChatCommand vr nm = \case
fileSize <- getFileSize filePath
unless (fileSize <= maxImageSize) $ throwChatError CEFileImageSize {filePath}
-- TODO include file description for preview
- processChatCommand vr nm $ APISendMessages sendRef False Nothing [composedMessage (Just f) (MCImage "" fixedImagePreview)]
+ processChatCommand cxt nm $ APISendMessages sendRef False Nothing [composedMessage (Just f) (MCImage "" fixedImagePreview)]
ForwardFile chatName fileId -> forwardFile chatName fileId SendFile
ForwardImage chatName fileId -> forwardFile chatName fileId SendImage
SendFileDescription _chatName _f -> throwCmdError "TODO"
@@ -3265,18 +3265,18 @@ processChatCommand vr nm = \case
| otherwise -> do
cancelSndFile user ftm fts True
cref_ <- withFastStore' $ \db -> lookupChatRefByFileId db user fileId
- aci_ <- withFastStore $ \db -> lookupChatItemByFileId db vr user fileId
+ aci_ <- withFastStore $ \db -> lookupChatItemByFileId db cxt user fileId
case (cref_, aci_) of
(Nothing, _) ->
pure $ CRSndFileCancelled user Nothing ftm fts
(Just (ChatRef CTDirect contactId _), Just aci) -> do
- (contact, sharedMsgId) <- withFastStore $ \db -> (,) <$> getContact db vr user contactId <*> getSharedMsgIdByFileId db userId fileId
+ (contact, sharedMsgId) <- withFastStore $ \db -> (,) <$> getContact db cxt user contactId <*> getSharedMsgIdByFileId db userId fileId
void . sendDirectContactMessage user contact $ XFileCancel sharedMsgId
pure $ CRSndFileCancelled user (Just aci) ftm fts
(Just (ChatRef CTGroup groupId scope), Just aci) -> do
- (gInfo, sharedMsgId) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user groupId <*> getSharedMsgIdByFileId db userId fileId
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
- recipients <- getGroupRecipients vr user gInfo chatScopeInfo groupKnockingVersion
+ (gInfo, sharedMsgId) <- withFastStore $ \db -> (,) <$> getGroupInfo db cxt user groupId <*> getSharedMsgIdByFileId db userId fileId
+ chatScopeInfo <- mapM (getChatScopeInfo cxt user) scope
+ recipients <- getGroupRecipients cxt user gInfo chatScopeInfo groupKnockingVersion
void . sendGroupMessage user gInfo scope recipients $ XFileCancel sharedMsgId
pure $ CRSndFileCancelled user (Just aci) ftm fts
(Just _, _) -> throwChatError $ CEFileInternal "invalid chat ref for file transfer"
@@ -3289,7 +3289,7 @@ processChatCommand vr nm = \case
| otherwise -> case xftpRcvFile of
Nothing -> do
cancelRcvFileTransfer user ftr
- ci <- withFastStore $ \db -> lookupChatItemByFileId db vr user fileId
+ ci <- withFastStore $ \db -> lookupChatItemByFileId db cxt user fileId
pure $ CRRcvFileCancelled user ci ftr
Just XFTPRcvFile {agentRcvFileId} -> do
forM_ (liveRcvFileTransferPath ftr) $ \filePath -> do
@@ -3300,7 +3300,7 @@ processChatCommand vr nm = \case
aci_ <- resetRcvCIFileStatus user fileId CIFSRcvInvitation
pure $ CRRcvFileCancelled user aci_ ftr
FileStatus fileId -> withUser $ \user -> do
- withFastStore (\db -> lookupChatItemByFileId db vr user fileId) >>= \case
+ withFastStore (\db -> lookupChatItemByFileId db cxt user fileId) >>= \case
Nothing -> do
fileStatus <- withFastStore $ \db -> getFileTransferProgress db user fileId
pure $ CRFileTransferStatus user fileStatus
@@ -3329,7 +3329,7 @@ processChatCommand vr nm = \case
let p = (fromLocalProfile profile :: Profile) {preferences = Just . setPreference f (Just allowed) $ preferences' user}
updateProfile user p
SetContactFeature (ACF f) cName allowed_ -> withUser $ \user -> do
- ct@Contact {userPreferences} <- withFastStore $ \db -> getContactByName db vr user cName
+ ct@Contact {userPreferences} <- withFastStore $ \db -> getContactByName db cxt user cName
let prefs' = setPreference f allowed_ $ Just userPreferences
updateContactPrefs user ct prefs'
SetGroupFeature (AGFNR f) gName enabled ->
@@ -3349,7 +3349,7 @@ processChatCommand vr nm = \case
p = (fromLocalProfile profile :: Profile) {preferences = Just . setPreference' SCFTimedMessages (Just pref) $ preferences' user}
updateProfile user p
SetContactTimedMessages cName timedMessagesEnabled_ -> withUser $ \user -> do
- ct@Contact {userPreferences = userPreferences@Preferences {timedMessages}} <- withFastStore $ \db -> getContactByName db vr user cName
+ ct@Contact {userPreferences = userPreferences@Preferences {timedMessages}} <- withFastStore $ \db -> getContactByName db cxt user cName
let currentTTL = timedMessages >>= \TimedMessagesPreference {ttl} -> ttl
pref_ = tmeToPref currentTTL <$> timedMessagesEnabled_
prefs' = setPreference' SCFTimedMessages pref_ $ Just userPreferences
@@ -3464,7 +3464,7 @@ processChatCommand vr nm = \case
_ -> throwCmdError "not supported"
pure $ ChatRef cType chatId Nothing
getSendAsGroup :: User -> ChatRef -> CM ShowGroupAsSender
- getSendAsGroup user' (ChatRef CTGroup chatId scope) = (`sendAsGroup'` scope) <$> withFastStore (\db -> getGroupInfo db vr user' chatId)
+ getSendAsGroup user' (ChatRef CTGroup chatId scope) = (`sendAsGroup'` scope) <$> withFastStore (\db -> getGroupInfo db cxt user' chatId)
getSendAsGroup _ _ = pure False
getChatRefAndMentions :: User -> ChatName -> Text -> CM (ChatRef, Map MemberName GroupMemberId)
getChatRefAndMentions user cName msg = do
@@ -3483,13 +3483,13 @@ processChatCommand vr nm = \case
checkStoreNotChanged :: CM ChatResponse -> CM ChatResponse
checkStoreNotChanged = ifM (asks chatStoreChanged >>= readTVarIO) (throwChatError CEChatStoreChanged)
withUserName :: UserName -> (UserId -> ChatCommand) -> CM ChatResponse
- withUserName uName cmd = withFastStore (`getUserIdByName` uName) >>= processChatCommand vr nm . cmd
+ withUserName uName cmd = withFastStore (`getUserIdByName` uName) >>= processChatCommand cxt nm . cmd
withContactName :: ContactName -> (ContactId -> ChatCommand) -> CM ChatResponse
withContactName cName cmd = withUser $ \user ->
- withFastStore (\db -> getContactIdByName db user cName) >>= processChatCommand vr nm . cmd
+ withFastStore (\db -> getContactIdByName db user cName) >>= processChatCommand cxt nm . cmd
withMemberName :: GroupName -> ContactName -> (GroupId -> GroupMemberId -> ChatCommand) -> CM ChatResponse
withMemberName gName mName cmd = withUser $ \user ->
- getGroupAndMemberId user gName mName >>= processChatCommand vr nm . uncurry cmd
+ getGroupAndMemberId user gName mName >>= processChatCommand cxt nm . uncurry cmd
getConnectionCode :: ConnId -> CM Text
getConnectionCode connId = verificationCode <$> withAgent (`getConnectionRatchetAdHash` connId)
verifyConnectionCode :: User -> Connection -> Maybe Text -> CM ChatResponse
@@ -3523,7 +3523,7 @@ processChatCommand vr nm = \case
-- 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
- withFastStore' (\db -> getConnectionEntityByConnReq db vr user cReqs) >>= \case
+ withFastStore' (\db -> getConnectionEntityByConnReq db cxt user cReqs) >>= \case
Nothing -> joinNewConn chatV
Just (RcvDirectMsgConnection conn@Connection {connStatus, contactConnInitiated, customUserProfileId} _ct_)
| connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV -- own connection link
@@ -3567,7 +3567,7 @@ processChatCommand vr nm = \case
ConnPrepared -> joinPreparedConn' xContactId conn (Just $ Just gInfo)
_ -> connect' groupLinkId xContactId (Just $ Just gInfo) -- why not "already connected" for host member?
Nothing ->
- withFastStore' (\db -> getConnReqContactXContactId db vr user cReqHash1 cReqHash2) >>= \case
+ withFastStore' (\db -> getConnReqContactXContactId db cxt user cReqHash1 cReqHash2) >>= \case
Right ct@Contact {activeConn} -> case groupLinkId of
Nothing -> case activeConn of
Just conn@Connection {connStatus = ConnPrepared, xContactId} -> joinPreparedConn' xContactId conn Nothing
@@ -3622,7 +3622,7 @@ processChatCommand vr nm = \case
let cReqHash = ConnReqUriHash . C.sha256Hash $ strEncode cReq
conn <- withFastStore' $ \db -> createConnReqConnection db userId connId (Just $ PCEContact ct) cReq cReqHash shortLink newXContactId (NewIncognito <$> incognitoProfile) Nothing subMode chatV pqSup
void $ joinContact user conn cReq incognitoProfile newXContactId Nothing Nothing Nothing pqSup
- ct' <- withStore $ \db -> getContact db vr user contactId
+ ct' <- withStore $ \db -> getContact db cxt user contactId
pure $ CRSentInvitationToContact user ct' incognitoProfile
Just conn@Connection {connStatus, xContactId = xContactId_, customUserProfileId} -> case connStatus of
ConnPrepared -> do
@@ -3631,14 +3631,14 @@ processChatCommand vr nm = \case
localIncognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId
let incognitoProfile = fromLocalProfile <$> localIncognitoProfile
void $ joinContact user conn cReq incognitoProfile xContactId Nothing Nothing Nothing PQSupportOn
- ct' <- withStore $ \db -> getContact db vr user contactId
+ ct' <- withStore $ \db -> getContact db cxt user contactId
pure $ CRSentInvitationToContact user ct' incognitoProfile
_ -> throwCmdError "contact already has connection"
connectToRelay :: User -> GroupInfo -> ShortLinkContact -> CM (ShortLinkContact, GroupMember, Either ChatError ())
connectToRelay user gInfo relayLink = do
gVar <- asks random
-- Save relayLink to re-use relay member record on retry (check by relayLink)
- relayMember <- withFastStore $ \db -> getCreateRelayForMember db vr gVar user gInfo relayLink
+ relayMember <- withFastStore $ \db -> getCreateRelayForMember db cxt gVar user gInfo relayLink
r <- tryAllErrors $ do
(fd@FixedLinkData {rootKey = relayKey, linkEntityId}, cData) <- getShortLinkConnReq nm user relayLink
relayLinkData_ <- liftIO $ decodeLinkUserData cData
@@ -3649,11 +3649,11 @@ processChatCommand vr nm = \case
let cReq = linkConnReq fd
relayLinkToConnect = CCLink cReq (Just relayLink)
void $ connectViaContact user (Just $ PCEGroup gInfo relayMember) (incognitoMembership gInfo) relayLinkToConnect Nothing Nothing
- relayMember' <- withFastStore $ \db -> getGroupMember db vr user (groupId' gInfo) (groupMemberId' relayMember)
+ relayMember' <- withFastStore $ \db -> getGroupMember db cxt user (groupId' gInfo) (groupMemberId' relayMember)
pure (relayLink, relayMember', r)
syncSubscriberRelays :: User -> GroupInfo -> [ShortLinkContact] -> CM ()
syncSubscriberRelays user gInfo currentRelayLinks = void . tryAllErrors $ do
- localRelayMembers <- withFastStore' $ \db -> getGroupRelayMembers db vr user gInfo
+ localRelayMembers <- withFastStore' $ \db -> getGroupRelayMembers db cxt user gInfo
let activeRelayMembers = filter memberCurrent localRelayMembers
memberRelayLink GroupMember {relayLink = rl} = rl
localRelayLinks = mapMaybe memberRelayLink activeRelayMembers
@@ -3723,7 +3723,7 @@ processChatCommand vr nm = \case
| otherwise = do
when (n /= n') $ checkValidName n'
-- read contacts before user update to correctly merge preferences
- contacts <- withFastStore' $ \db -> getUserContacts db vr user
+ contacts <- withFastStore' $ \db -> getUserContacts db cxt user
user' <- updateUser
asks currentUser >>= atomically . (`writeTVar` Just user')
withChatLock "updateProfile" $ do
@@ -3775,7 +3775,7 @@ processChatCommand vr nm = \case
(conn, MsgFlags {notification = hasNotification XInfo_}, (vrValue msgBody, [msgId]))
setMyAddressData :: User -> UserContactLink -> CM UserContactLink
setMyAddressData user@User {userChatRelay} ucl@UserContactLink {userContactLinkId, connLinkContact = CCLink connFullLink _sLnk_, addressSettings} = do
- conn <- withFastStore $ \db -> getUserAddressConnection db vr user
+ conn <- withFastStore $ \db -> getUserAddressConnection db cxt user
let shortLinkProfile = userProfileDirect user Nothing Nothing True
-- TODO [short links] do not save address to server if data did not change, spinners, error handling
userData
@@ -3809,12 +3809,12 @@ processChatCommand vr nm = \case
gInfo' <- withStore $ \db -> updateGroupProfile db user gInfo p'
msg <- case businessChat of
Just BusinessChatInfo {businessId} -> do
- ms <- withStore' $ \db -> getGroupMembers db vr user gInfo'
+ ms <- withStore' $ \db -> getGroupMembers db cxt user gInfo'
let (newMs, oldMs) = partition (\m -> maxVersion (memberChatVRange m) >= businessChatPrefsVersion) ms
-- this is a fallback to send the members with the old version correct profile of the business when preferences change
unless (null oldMs) $ do
GroupMember {memberProfile = LocalProfile {displayName, fullName, shortDescr, image}} <-
- withStore $ \db -> getGroupMemberByMemberId db vr user gInfo' businessId
+ withStore $ \db -> getGroupMemberByMemberId db cxt user gInfo' businessId
let p'' = p' {displayName, fullName, shortDescr, image} :: GroupProfile
recipients = filter memberCurrentOrPending oldMs
void $ sendGroupMessage user gInfo' Nothing recipients (XGrpInfo p'')
@@ -3827,9 +3827,9 @@ processChatCommand vr nm = \case
sendGroupMessage user gInfo' Nothing recipients (XGrpInfo p')
where
getRecipients
- | useRelays' gInfo' = withFastStore' $ \db -> getGroupRelayMembers db vr user gInfo'
+ | useRelays' gInfo' = withFastStore' $ \db -> getGroupRelayMembers db cxt user gInfo'
| otherwise = do
- ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo'
+ ms <- withFastStore' $ \db -> getGroupMembers db cxt user gInfo'
pure $ filter memberCurrentOrPending ms
let cd = CDGroupSnd gInfo' Nothing
unless (sameGroupProfileInfo p p') $ do
@@ -3890,13 +3890,13 @@ processChatCommand vr nm = \case
updateGroupProfileByName :: GroupName -> (GroupProfile -> GroupProfile) -> CM ChatResponse
updateGroupProfileByName gName update = withUser $ \user -> do
gInfo@GroupInfo {groupProfile = p} <- withStore $ \db ->
- getGroupIdByName db user gName >>= getGroupInfo db vr user
+ getGroupIdByName db user gName >>= getGroupInfo db cxt user
runUpdateGroupProfile user gInfo $ update p
withCurrentCall :: ContactId -> (User -> Contact -> Call -> CM (Maybe Call)) -> CM ChatResponse
withCurrentCall ctId action = do
(user, ct) <- withStore $ \db -> do
user <- getUserByContactId db ctId
- (user,) <$> getContact db vr user ctId
+ (user,) <$> getContact db cxt user ctId
calls <- asks currentCalls
withContactLock "currentCall" ctId $
atomically (TM.lookup ctId calls) >>= \case
@@ -3938,7 +3938,7 @@ processChatCommand vr nm = \case
FTSnd {fileTransferMeta = FileTransferMeta {filePath, xftpSndFile}} -> forward filePath $ xftpSndFile >>= \XFTPSndFile {cryptoArgs} -> cryptoArgs
_ -> throwChatError CEFileNotReceived {fileId}
where
- forward path cfArgs = processChatCommand vr nm $ sendCommand chatName $ CryptoFile path cfArgs
+ forward path cfArgs = processChatCommand cxt nm $ sendCommand chatName $ CryptoFile path cfArgs
getGroupAndMemberId :: User -> GroupName -> ContactName -> CM (GroupId, GroupMemberId)
getGroupAndMemberId user gName groupMemberName =
withStore $ \db -> do
@@ -3950,7 +3950,7 @@ processChatCommand vr nm = \case
checkValidName displayName
-- [incognito] generate incognito profile for group membership
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
- withFastStore $ \db -> createNewGroup db vr user gProfile incognitoProfile useRelays memberId groupKeys_ publicMemberCount_
+ withFastStore $ \db -> createNewGroup db cxt user gProfile incognitoProfile useRelays memberId groupKeys_ publicMemberCount_
createNewGroupItems :: User -> GroupInfo -> CM ()
createNewGroupItems user gInfo = do
let cd = CDGroupSnd gInfo Nothing
@@ -3993,9 +3993,9 @@ processChatCommand vr nm = \case
subMode <- chatReadVar subscriptionMode
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq PQSupportOff
(relayMember, conn, groupRelay) <- withFastStore $ \db -> do
- relayMember <- createRelayForOwner db vr gVar user gInfo relay
+ relayMember <- createRelayForOwner db cxt gVar user gInfo relay
groupRelay <- createGroupRelayRecord db gInfo relayMember relay
- conn <- createRelayConnection db vr user (groupMemberId' relayMember) connId ConnPrepared chatV subMode
+ conn <- createRelayConnection db cxt user (groupMemberId' relayMember) connId ConnPrepared chatV subMode
pure (relayMember, conn, groupRelay)
let GroupMember {memberRole = userRole, memberId = userMemberId} = membership
allowSimplexLinks = groupFeatureUserAllowed SGFSimplexLinks gInfo
@@ -4067,15 +4067,15 @@ processChatCommand vr nm = \case
(chatId, chatSettings) <- case cType of
CTDirect -> withFastStore $ \db -> do
ctId <- getContactIdByName db user name
- Contact {chatSettings} <- getContact db vr user ctId
+ Contact {chatSettings} <- getContact db cxt user ctId
pure (ctId, chatSettings)
CTGroup ->
withFastStore $ \db -> do
gId <- getGroupIdByName db user name
- GroupInfo {chatSettings} <- getGroupInfo db vr user gId
+ GroupInfo {chatSettings} <- getGroupInfo db cxt user gId
pure (gId, chatSettings)
_ -> throwCmdError "not supported"
- processChatCommand vr nm $ APISetChatSettings (ChatRef cType chatId Nothing) $ updateSettings chatSettings
+ processChatCommand cxt nm $ APISetChatSettings (ChatRef cType chatId Nothing) $ updateSettings chatSettings
connectPlan :: User -> ConnectTarget -> Bool -> Maybe LinkOwnerSig -> CM (ACreatedConnLink, ConnectionPlan)
connectPlan user ct resolveKnown sig_ = case ct of
CTLink l -> connectPlanLink user l resolveKnown sig_
@@ -4095,10 +4095,10 @@ processChatCommand vr nm = \case
where
knownLinkPlans l' = withFastStore $ \db -> do
let inv cReq = ACCL SCMInvitation $ CCLink cReq (Just l')
- liftIO (getConnectionEntityViaShortLink db vr user l') >>= \case
+ liftIO (getConnectionEntityViaShortLink db cxt user l') >>= \case
Just (cReq, ent) -> pure $ Just (inv cReq, invitationEntityPlan Nothing Nothing ent)
-- deleted contact is returned as known, as invitation link cannot be re-used too connect anyway
- Nothing -> bimap inv (CPInvitationLink . ILPKnown) <$$> getContactViaShortLinkToConnect db vr user l'
+ Nothing -> bimap inv (CPInvitationLink . ILPKnown) <$$> getContactViaShortLinkToConnect db cxt user l'
invitationReqAndPlan cReq sLnk_ cld ov = do
plan <- invitationRequestPlan user cReq cld ov `catchAllErrors` (pure . CPError)
pure (ACCL SCMInvitation (CCLink cReq sLnk_), plan)
@@ -4113,7 +4113,7 @@ processChatCommand vr nm = \case
Just r -> pure r
Nothing -> do
(FixedLinkData {linkConnReq = cReq, rootKey}, cData) <- getShortLinkConnReq nm user l'
- withFastStore' (\db -> getContactWithoutConnViaShortAddress db vr user l') >>= \case
+ withFastStore' (\db -> getContactWithoutConnViaShortAddress db cxt user l') >>= \case
Just ct' | not (contactDeleted ct') -> pure (con cReq, CPContactAddress (CAPContactViaAddress ct'))
_ -> do
contactSLinkData_ <- liftIO $ decodeLinkUserData cData
@@ -4126,9 +4126,9 @@ processChatCommand vr nm = \case
liftIO (getUserContactLinkViaShortLink db user l') >>= \case
Just UserContactLink {connLinkContact = CCLink cReq _} -> pure $ Just (con cReq, CPContactAddress CAPOwnLink)
Nothing ->
- getContactViaShortLinkToConnect db vr user l' >>= \case
+ getContactViaShortLinkToConnect db cxt user l' >>= \case
Just (cReq, ct') -> pure $ if contactDeleted ct' then Nothing else Just (con cReq, CPContactAddress (CAPKnown ct'))
- Nothing -> (gPlan =<<) <$> getGroupViaShortLinkToConnect db vr user l'
+ Nothing -> (gPlan =<<) <$> getGroupViaShortLinkToConnect db cxt user l'
CCTGroup -> groupShortLinkPlan
CCTChannel -> groupShortLinkPlan
CCTRelay -> throwCmdError "chat relay links are not supported in this version"
@@ -4164,9 +4164,9 @@ processChatCommand vr nm = \case
Just GroupShortLinkData {groupProfile = GroupProfile {publicGroup = Just PublicGroupProfile {groupType}}} -> groupType /= GTChannel
_ -> False
knownLinkPlans = withFastStore $ \db ->
- liftIO (getGroupInfoViaUserShortLink db vr user l') >>= \case
+ liftIO (getGroupInfoViaUserShortLink db cxt user l') >>= \case
Just (cReq, g) -> pure $ Just (con cReq, CPGroupLink (GLPOwnLink g))
- Nothing -> (gPlan =<<) <$> getGroupViaShortLinkToConnect db vr user l'
+ Nothing -> (gPlan =<<) <$> getGroupViaShortLinkToConnect db cxt user l'
resolveKnownGroup g = do
(fd@FixedLinkData {rootKey = rk}, cData@(ContactLinkData _ UserContactData {owners})) <- getShortLinkConnReq' nm user l'
groupSLinkData_ <- liftIO $ decodeLinkUserData cData
@@ -4182,7 +4182,7 @@ processChatCommand vr nm = \case
-- bytes via strEncode, so an `@contact` lookup can never match a group
-- row (and vice versa). Dispatch on nameType up front to skip a probe.
NTContact -> do
- ct_ <- withFastStore $ \db -> getContactBySimplexName db vr user ni
+ ct_ <- withFastStore $ \db -> getContactBySimplexName db cxt user ni
case ct_ of
Just ct -> case preparedContact ct of
Just PreparedContact {connLinkToConnect} -> pure (connLinkToConnect, CPContactAddress (CAPKnown ct))
@@ -4191,7 +4191,7 @@ processChatCommand vr nm = \case
Nothing -> throwChatError $ CESimplexNameUnprepared ni
Nothing -> resolveAndDispatch
NTPublicGroup -> do
- g_ <- withFastStore $ \db -> getGroupInfoBySimplexName db vr user ni
+ g_ <- withFastStore $ \db -> getGroupInfoBySimplexName db cxt user ni
case g_ of
-- Mirror gPlan at line ~4133 in the link-based path: a removed member is not a
-- known-and-reconnectable group; treat as "not found" so the caller can try elsewhere.
@@ -4205,7 +4205,7 @@ processChatCommand vr nm = \case
resolveAndDispatch :: CM (ACreatedConnLink, ConnectionPlan)
resolveAndDispatch =
resolveOnUserServers user nameDomain >>= \case
- Right nr -> dispatchResolvedRecord vr nm user ni nr
+ Right nr -> dispatchResolvedRecord cxt nm user ni nr
Left re -> throwError $ resolveErrorToChatError ni re
connectWithPlan :: User -> IncognitoEnabled -> ACreatedConnLink -> ConnectionPlan -> CM ChatResponse
connectWithPlan user@User {userId} incognito ccLink plan
@@ -4213,13 +4213,13 @@ processChatCommand vr nm = \case
case plan of CPError e -> eToView e; _ -> pure ()
case plan of
CPContactAddress (CAPContactViaAddress Contact {contactId}) ->
- processChatCommand vr nm $ APIConnectContactViaAddress userId incognito contactId
- _ -> processChatCommand vr nm $ APIConnect userId incognito $ Just ccLink
+ processChatCommand cxt nm $ APIConnectContactViaAddress userId incognito contactId
+ _ -> processChatCommand cxt nm $ APIConnect userId incognito $ Just ccLink
| otherwise = pure $ CRConnectionPlan user ccLink plan
invitationRequestPlan :: User -> ConnReqInvitation -> Maybe ContactShortLinkData -> Maybe OwnerVerification -> CM ConnectionPlan
invitationRequestPlan user cReq cld ov = do
maybe (CPInvitationLink (ILPOk cld ov)) (invitationEntityPlan cld ov)
- <$> withFastStore' (\db -> getConnectionEntityByConnReq db vr user $ invCReqSchemas cReq)
+ <$> withFastStore' (\db -> getConnectionEntityByConnReq db cxt user $ invCReqSchemas cReq)
where
invCReqSchemas :: ConnReqInvitation -> (ConnReqInvitation, ConnReqInvitation)
invCReqSchemas (CRInvitationUri crData e2e) =
@@ -4251,9 +4251,9 @@ processChatCommand vr nm = \case
withFastStore' (\db -> getUserContactLinkByConnReq db user cReqSchemas) >>= \case
Just _ -> pure $ CPContactAddress CAPOwnLink
Nothing ->
- withFastStore' (\db -> getContactConnEntityByConnReqHash db vr user cReqHashes) >>= \case
+ withFastStore' (\db -> getContactConnEntityByConnReqHash db cxt user cReqHashes) >>= \case
Nothing ->
- withFastStore' (\db -> getContactWithoutConnViaAddress db vr user cReqSchemas) >>= \case
+ withFastStore' (\db -> getContactWithoutConnViaAddress db cxt user cReqSchemas) >>= \case
Just ct | not (contactDeleted ct) -> pure $ CPContactAddress (CAPContactViaAddress ct)
_ -> pure $ CPContactAddress (CAPOk cld ov)
Just (RcvDirectMsgConnection Connection {connStatus} Nothing)
@@ -4270,11 +4270,11 @@ processChatCommand vr nm = \case
groupJoinRequestPlan user (CRContactUri crData) linkInfo gld ov = do
let cReqSchemas = contactCReqSchemas crData
cReqHashes = bimap contactCReqHash contactCReqHash cReqSchemas
- withFastStore' (\db -> getGroupInfoByUserContactLinkConnReq db vr user cReqSchemas) >>= \case
+ withFastStore' (\db -> getGroupInfoByUserContactLinkConnReq db cxt user cReqSchemas) >>= \case
Just g -> pure $ CPGroupLink (GLPOwnLink g)
Nothing -> do
- connEnt_ <- withFastStore' $ \db -> getContactConnEntityByConnReqHash db vr user cReqHashes
- gInfo_ <- withFastStore' $ \db -> getGroupInfoByGroupLinkHash db vr user cReqHashes
+ connEnt_ <- withFastStore' $ \db -> getContactConnEntityByConnReqHash db cxt user cReqHashes
+ gInfo_ <- withFastStore' $ \db -> getGroupInfoByGroupLinkHash db cxt user cReqHashes
case (gInfo_, connEnt_) of
(Nothing, Nothing) -> pure $ CPGroupLink (GLPOk linkInfo gld ov)
-- TODO [short links] RcvDirectMsgConnection branches are deprecated? (old group link protocol?)
@@ -4329,7 +4329,7 @@ processChatCommand vr nm = \case
shortenShortLink' =<< withAgent (\a -> setConnShortLink a nm (aConnId' conn) SCMInvitation userLinkData Nothing)
updateCIGroupInvitationStatus :: User -> GroupInfo -> CIGroupInvitationStatus -> CM ()
updateCIGroupInvitationStatus user GroupInfo {groupId} newStatus = do
- AChatItem _ _ cInfo ChatItem {content, meta = CIMeta {itemId}} <- withFastStore $ \db -> getChatItemByGroupId db vr user groupId
+ AChatItem _ _ cInfo ChatItem {content, meta = CIMeta {itemId}} <- withFastStore $ \db -> getChatItemByGroupId db cxt user groupId
case (cInfo, content) of
(DirectChat ct@Contact {contactId}, CIRcvGroupInvitation ciGroupInv@CIGroupInvitation {status} memRole)
| status == CIGISPending -> do
@@ -4352,7 +4352,7 @@ processChatCommand vr nm = \case
sendContactContentMessages :: User -> ContactId -> Bool -> Maybe Int -> NonEmpty ComposedMessageReq -> CM ChatResponse
sendContactContentMessages user contactId live itemTTL cmrs = do
assertMultiSendable live cmrs
- ct <- withFastStore $ \db -> getContact db vr user contactId
+ ct <- withFastStore $ \db -> getContact db cxt user contactId
assertDirectAllowed user MDSnd ct XMsgNew_
assertVoiceAllowed ct
processComposedMessages ct
@@ -4409,8 +4409,8 @@ processChatCommand vr nm = \case
sendGroupContentMessages :: User -> GroupInfo -> Maybe GroupChatScope -> ShowGroupAsSender -> Bool -> Maybe Int -> NonEmpty ComposedMessageReq -> CM ChatResponse
sendGroupContentMessages user gInfo scope showGroupAsSender live itemTTL cmrs = do
assertMultiSendable live cmrs
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
- recipients <- getGroupRecipients vr user gInfo chatScopeInfo modsCompatVersion
+ chatScopeInfo <- mapM (getChatScopeInfo cxt user) scope
+ recipients <- getGroupRecipients cxt user gInfo chatScopeInfo modsCompatVersion
sendGroupContentMessages_ user gInfo scope showGroupAsSender chatScopeInfo recipients live itemTTL cmrs
where
hasReport = any (\(ComposedMessage {msgContent}, _, _, _) -> isReport msgContent) cmrs
@@ -4555,7 +4555,7 @@ processChatCommand vr nm = \case
throwError err
getCommandDirectChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (Contact, [CChatItem 'CTDirect])
getCommandDirectChatItems user ctId itemIds = do
- ct <- withFastStore $ \db -> getContact db vr user ctId
+ ct <- withFastStore $ \db -> getContact db cxt user ctId
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getDirectCI db) (L.toList itemIds))
unless (null errs) $ toView $ CEvtChatErrors errs
pure (ct, items)
@@ -4564,7 +4564,7 @@ processChatCommand vr nm = \case
getDirectCI db itemId = runExceptT . withExceptT ChatErrorStore $ getDirectChatItem db user ctId itemId
getCommandGroupChatItems :: User -> Int64 -> NonEmpty ChatItemId -> CM (GroupInfo, [CChatItem 'CTGroup])
getCommandGroupChatItems user gId itemIds = do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user gId
(errs, items) <- lift $ partitionEithers <$> withStoreBatch (\db -> map (getGroupCI db gInfo) (L.toList itemIds))
unless (null errs) $ toView $ CEvtChatErrors errs
pure (gInfo, items)
@@ -4623,7 +4623,7 @@ processChatCommand vr nm = \case
withSendRef user chatRef a = case chatRef of
ChatRef CTDirect cId _ -> a $ SRDirect cId
ChatRef CTGroup gId scope -> do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId
+ gInfo <- withFastStore $ \db -> getGroupInfo db cxt user gId
a $ SRGroup gId scope (sendAsGroup' gInfo scope)
_ -> throwCmdError "not supported"
getSharedMsgId :: CM SharedMsgId
@@ -4677,8 +4677,8 @@ resolveOnUserServers user@User {userId} domain = do
-- via the existing 'createPreparedContact' / 'createPreparedGroup' simplex_name
-- parameter (introduced for the local-prepare path, see commit c6f26150), so
-- the resolver hit reuses the same DB write path as a local-prepare hit.
-dispatchResolvedRecord :: VersionRangeChat -> NetworkRequestMode -> User -> SimplexNameInfo -> NameRecord -> CM (ACreatedConnLink, ConnectionPlan)
-dispatchResolvedRecord vr nm user ni@SimplexNameInfo {nameType} NameRecord {nrSimplexChannel, nrSimplexContact} = do
+dispatchResolvedRecord :: StoreCxt -> NetworkRequestMode -> User -> SimplexNameInfo -> NameRecord -> CM (ACreatedConnLink, ConnectionPlan)
+dispatchResolvedRecord cxt nm user ni@SimplexNameInfo {nameType} NameRecord {nrSimplexChannel, nrSimplexContact} = do
lnk <- liftEither $ firstNameLink nameType nrSimplexChannel nrSimplexContact ni
acl <- liftEither $ first (chatErrorAgent . AGENT . A_LINK) $ strDecode (encodeUtf8 lnk)
prepareAndPlan acl
@@ -4701,7 +4701,7 @@ dispatchResolvedRecord vr nm user ni@SimplexNameInfo {nameType} NameRecord {nrSi
liftIO (decodeLinkUserData cData) >>= maybe (throwError $ chatErrorAgent $ AGENT $ A_LINK "could not decode contact profile from RSLV link") pure
let ccLink = CCLink cReq (Just l')
accLink = ACCL SCMContact ccLink
- (ct, displaced_) <- withStore $ \db -> createPreparedContact db vr user profile accLink Nothing (Just ni)
+ (ct, displaced_) <- withStore $ \db -> createPreparedContact db cxt user profile accLink Nothing (Just ni)
let Profile {simplexName = pSimplexName} = profile
Contact {localDisplayName = newLDN} = ct
surfaceSimplexNameConflict user pSimplexName displaced_ SNCEContact newLDN
@@ -4717,7 +4717,7 @@ dispatchResolvedRecord vr nm user ni@SimplexNameInfo {nameType} NameRecord {nrSi
subRole <- if useRelays then asks $ channelSubscriberRole . config else pure GRMember
gVar <- asks random
let ccLink = CCLink cReq (Just l')
- (g, _hostMember_) <- withStore $ \db -> createPreparedGroup db gVar vr user groupProfile False ccLink Nothing useRelays subRole publicMemberCount_ (Just ni)
+ (g, _hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile False ccLink Nothing useRelays subRole publicMemberCount_ (Just ni)
pure (ACCL SCMContact ccLink, CPGroupLink (GLPKnown g (BoolDef False) Nothing (ListDef [])))
-- Mirror the inline 'serverShortLink' helper defined in 'processChatCommand'
-- where this dispatch is invoked: RSLV-supplied short links may carry the
@@ -4779,8 +4779,8 @@ linksMatch resolved stored = case strDecode (encodeUtf8 resolved) :: Either Stri
-- Throws CESimplexNameNotFound when the row has no claim to verify.
apiVerifySimplexName :: User -> ChatRef -> CM ChatResponse
apiVerifySimplexName user chatRef = do
- vr <- chatVersionRange
- (claim, storedLink, persistVerified) <- loadClaimAndLink vr
+ cxt <- chatStoreCxt
+ (claim, storedLink, persistVerified) <- loadClaimAndLink cxt
let domain = (\SimplexNameInfo {nameDomain} -> nameDomain) claim
nameType' = (\SimplexNameInfo {nameType} -> nameType) claim
resolveOnUserServers user domain >>= \case
@@ -4805,16 +4805,16 @@ apiVerifySimplexName user chatRef = do
-- Returns the claim to verify, the peer's stored link, and a callback that
-- persists the verified_at timestamp to the appropriate table. Throws a
-- command error when the row has no claim or no link (nothing to verify).
- loadClaimAndLink :: VersionRangeChat -> CM (SimplexNameInfo, ConnLinkContact, DB.Connection -> UTCTime -> IO ())
- loadClaimAndLink vr = case chatRef of
+ loadClaimAndLink :: StoreCxt -> CM (SimplexNameInfo, ConnLinkContact, DB.Connection -> UTCTime -> IO ())
+ loadClaimAndLink cxt = case chatRef of
ChatRef CTDirect cId _ -> do
- ct <- withFastStore $ \db -> getContact db vr user cId
+ ct <- withFastStore $ \db -> getContact db cxt user cId
let Contact {contactId, simplexName = ctSimplexName, profile = LocalProfile {contactLink}} = ct
claim <- maybe (throwCmdError "contact has no simplex_name to verify") pure ctSimplexName
lnk <- maybe (throwCmdError "contact has no stored link to verify against") pure contactLink
pure (claim, lnk, \db ts -> setContactSimplexNameVerifiedAt db user contactId ts)
ChatRef CTGroup gId _ -> do
- g <- withFastStore $ \db -> getGroupInfo db vr user gId
+ g <- withFastStore $ \db -> getGroupInfo db cxt user gId
let GroupInfo {groupId, simplexName = gSimplexName, preparedGroup} = g
claim <- maybe (throwCmdError "group has no simplex_name to verify") pure gSimplexName
PreparedGroup {connLinkToConnect = CCLink cReq shortLink_} <-
@@ -5033,17 +5033,17 @@ cleanupManager = do
timedItems <- withStore' $ \db -> getTimedItems db user startTimedThreadCutoff
forM_ timedItems $ \(itemRef, deleteAt) -> startTimedItemThread user itemRef deleteAt `catchAllErrors` const (pure ())
cleanupDeletedContacts user = do
- vr <- chatVersionRange
- contacts <- withStore' $ \db -> getDeletedContacts db vr user
+ cxt <- chatStoreCxt
+ contacts <- withStore' $ \db -> getDeletedContacts db cxt user
forM_ contacts $ \ct ->
withStore (\db -> deleteContactWithoutGroups db user ct)
`catchAllErrors` eToView
cleanupInProgressGroups user = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
ts <- liftIO getCurrentTime
-- older than 30 minutes to avoid deleting a newly created group
let cutoffTs = addUTCTime (- 1800) ts
- inProgressGroups <- withStore' $ \db -> getInProgressGroups db vr user cutoffTs
+ inProgressGroups <- withStore' $ \db -> getInProgressGroups db cxt user cutoffTs
forM_ inProgressGroups $ \gInfo ->
deleteInProgressGroup user gInfo `catchAllErrors` eToView
cleanupStaleRelayTestConns user = do
@@ -5054,10 +5054,10 @@ cleanupManager = do
deleteAgentConnectionAsync acId
withStore' $ \db -> deleteConnectionByAgentConnId db user acId
cleanupRemovedMembers user = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
ts <- liftIO getCurrentTime
let cutoffTs = addUTCTime (-nominalDay) ts
- removedMembers <- withStore' $ \db -> getRemovedMembersToCleanup db vr user cutoffTs
+ removedMembers <- withStore' $ \db -> getRemovedMembersToCleanup db cxt user cutoffTs
forM_ removedMembers $ \m ->
withStore' (\db -> deleteGroupMember db user m) `catchAllErrors` eToView
cleanupMessages = do
@@ -5095,8 +5095,8 @@ runRelayGroupLinkChecks user = do
liftIO $ threadDelay' $ diffToMicroseconds interval
where
checkRelayServedGroups = do
- vr <- chatVersionRange
- relayGroups <- withStore' $ \db -> getRelayServedGroups db vr user
+ cxt <- chatStoreCxt
+ relayGroups <- withStore' $ \db -> getRelayServedGroups db cxt user
forM_ relayGroups $ \gInfo@GroupInfo {groupProfile = gp} -> flip catchAllErrors eToView $ do
case publicGroup gp of
Just PublicGroupProfile {groupLink = sLnk} -> do
@@ -5114,24 +5114,24 @@ runRelayGroupLinkChecks user = do
_ -> pure ()
_ -> pure ()
checkRelayInactiveGroups = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
ttl <- asks (relayInactiveTTL . config)
- inactiveGroups <- withStore' $ \db -> getRelayInactiveGroups db vr user ttl
+ inactiveGroups <- withStore' $ \db -> getRelayInactiveGroups db cxt user ttl
forM_ inactiveGroups $ \gInfo -> flip catchAllErrors eToView $
deleteGroupConnections user gInfo False
expireChatItems :: User -> Int64 -> Bool -> CM ()
expireChatItems user@User {userId} globalTTL sync = do
currentTs <- liftIO getCurrentTime
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
-- this is to keep group messages created during last 12 hours even if they're expired according to item_ts
let createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs
lift waitChatStartedAndActivated
contactIds <- withStore' $ \db -> getUserContactsToExpire db user globalTTL
- loop contactIds $ expireContactChatItems user vr globalTTL
+ loop contactIds $ expireContactChatItems user cxt globalTTL
lift waitChatStartedAndActivated
groupIds <- withStore' $ \db -> getUserGroupsToExpire db user globalTTL
- loop groupIds $ expireGroupChatItems user vr globalTTL createdAtCutoff
+ loop groupIds $ expireGroupChatItems user cxt globalTTL createdAtCutoff
where
loop :: [Int64] -> (Int64 -> CM ()) -> CM ()
loop [] _ = pure ()
@@ -5147,11 +5147,11 @@ expireChatItems user@User {userId} globalTTL sync = do
expire <- atomically $ TM.lookup userId expireFlags
when (expire == Just True) $ threadDelay 100000 >> a
-expireContactChatItems :: User -> VersionRangeChat -> Int64 -> ContactId -> CM ()
-expireContactChatItems user vr globalTTL ctId =
+expireContactChatItems :: User -> StoreCxt -> Int64 -> ContactId -> CM ()
+expireContactChatItems user cxt globalTTL ctId =
-- reading contacts and groups inside the loop,
-- to allow ttl changing while processing and to reduce memory usage
- tryAllErrors (withStore $ \db -> getContact db vr user ctId) >>= mapM_ process
+ tryAllErrors (withStore $ \db -> getContact db cxt user ctId) >>= mapM_ process
where
process ct@Contact {chatItemTTL} =
withExpirationDate globalTTL chatItemTTL $ \expirationDate -> do
@@ -5160,9 +5160,9 @@ expireContactChatItems user vr globalTTL ctId =
deleteCIFiles user filesInfo
withStore' $ \db -> deleteContactExpiredCIs db user ct expirationDate
-expireGroupChatItems :: User -> VersionRangeChat -> Int64 -> UTCTime -> GroupId -> CM ()
-expireGroupChatItems user vr globalTTL createdAtCutoff groupId =
- tryAllErrors (withStore $ \db -> getGroupInfo db vr user groupId) >>= mapM_ process
+expireGroupChatItems :: User -> StoreCxt -> Int64 -> UTCTime -> GroupId -> CM ()
+expireGroupChatItems user cxt globalTTL createdAtCutoff groupId =
+ tryAllErrors (withStore $ \db -> getGroupInfo db cxt user groupId) >>= mapM_ process
where
process gInfo@GroupInfo {chatItemTTL} =
withExpirationDate globalTTL chatItemTTL $ \expirationDate -> do
@@ -5170,7 +5170,7 @@ expireGroupChatItems user vr globalTTL createdAtCutoff groupId =
filesInfo <- withStore' $ \db -> getGroupExpiredFileInfo db user gInfo expirationDate createdAtCutoff
deleteCIFiles user filesInfo
withStore' $ \db -> deleteGroupExpiredCIs db user gInfo expirationDate createdAtCutoff
- membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db vr user gInfo
+ membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db cxt user gInfo
forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m
withExpirationDate :: Int64 -> Maybe Int64 -> (UTCTime -> CM ()) -> CM ()
diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs
index a2f12aff83..8317ac81d1 100644
--- a/src/Simplex/Chat/Library/Internal.hs
+++ b/src/Simplex/Chat/Library/Internal.hs
@@ -482,12 +482,12 @@ deleteGroupCIs user gInfo chatScopeInfo items byGroupMember_ deletedTs = do
deleteCIFiles user ciFilesInfo
(errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (deleteItem db) items)
unless (null errs) $ toView $ CEvtChatErrors errs
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
deletions' <- case chatScopeInfo of
Nothing -> pure deletions
Just scopeInfo@GCSIMemberSupport {groupMember_} -> do
let decStats = countDeletedUnreadItems groupMember_ deletions
- gInfo' <- withFastStore' $ \db -> updateGroupScopeUnreadStats db vr user gInfo scopeInfo decStats
+ gInfo' <- withFastStore' $ \db -> updateGroupScopeUnreadStats db cxt user gInfo scopeInfo decStats
pure $ map (updateDeletionGroupInfo gInfo') deletions
pure deletions'
where
@@ -696,7 +696,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI
unless (fileStatus == RFSNew) $ case fileStatus of
RFSCancelled _ -> throwChatError $ CEFileCancelled fName
_ -> throwChatError $ CEFileAlreadyReceiving fName
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
case (xftpRcvFile, fileConnReq) of
-- XFTP
(Just XFTPRcvFile {userApprovedRelays = approvedBeforeReady}, _) -> do
@@ -705,7 +705,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI
(ci, rfd) <- withStore $ \db -> do
-- marking file as accepted and reading description in the same transaction
-- to prevent race condition with appending description
- ci <- xftpAcceptRcvFT db vr user fileId filePath userApproved
+ ci <- xftpAcceptRcvFT db cxt user fileId filePath userApproved
rfd <- getRcvFileDescrByRcvFileId db fileId
pure (ci, rfd)
receiveViaCompleteFD user fileId rfd userApproved cryptoArgs
@@ -716,10 +716,10 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI
chatRef <- withStore $ \db -> getChatRefByFileId db user fileId
case (chatRef, grpMemberId) of
(ChatRef CTDirect contactId _, Nothing) -> do
- ct <- withStore $ \db -> getContact db vr user contactId
+ ct <- withStore $ \db -> getContact db cxt user contactId
acceptFile $ \msg -> void $ sendDirectContactMessage user ct msg
(ChatRef CTGroup groupId _, Just memId) -> do
- GroupMember {activeConn} <- withStore $ \db -> getGroupMember db vr user groupId memId
+ GroupMember {activeConn} <- withStore $ \db -> getGroupMember db cxt user groupId memId
case activeConn of
Just conn -> do
acceptFile $ \msg -> void $ sendDirectMemberMessage conn msg groupId
@@ -730,12 +730,12 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI
acceptFile send = do
filePath <- getRcvFilePath fileId filePath_ fName True
inline <- receiveInline
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
if
| inline -> do
-- accepting inline
(ci, sharedMsgId) <- withStore $ \db ->
- liftM2 (,) (acceptRcvInlineFT db vr user fileId filePath) (getSharedMsgIdByFileId db userId fileId)
+ liftM2 (,) (acceptRcvInlineFT db cxt user fileId filePath) (getSharedMsgIdByFileId db userId fileId)
send $ XFileAcptInv sharedMsgId Nothing fName
pure ci
| fileInline == Just IFMSent -> throwChatError $ CEFileAlreadyReceiving fName
@@ -811,13 +811,13 @@ getNetworkConfig = withAgent' $ liftIO . getFastNetworkConfig
resetRcvCIFileStatus :: User -> FileTransferId -> CIFileStatus 'MDRcv -> CM (Maybe AChatItem)
resetRcvCIFileStatus user fileId ciFileStatus = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
withStore $ \db -> do
liftIO $ do
updateCIFileStatus db user fileId ciFileStatus
updateRcvFileStatus db fileId FSNew
updateRcvFileAgentId db fileId Nothing
- lookupChatItemByFileId db vr user fileId
+ lookupChatItemByFileId db cxt user fileId
receiveViaURI :: User -> FileDescriptionURI -> CryptoFile -> CM RcvFileTransfer
receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile {cryptoArgs} = do
@@ -835,11 +835,11 @@ receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile
startReceivingFile :: User -> FileTransferId -> CM ()
startReceivingFile user fileId = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
ci <- withStore $ \db -> do
liftIO $ updateRcvFileStatus db fileId FSConnected
liftIO $ updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1
- getChatItemByFileId db vr user fileId
+ getChatItemByFileId db cxt user fileId
toView $ CEvtRcvFileStart user ci
getRcvFilePath :: FileTransferId -> Maybe FilePath -> String -> Bool -> CM FilePath
@@ -890,8 +890,8 @@ acceptContactRequest nm user@User {userId} UserContactRequest {agentInvitationId
subMode <- chatReadVar subscriptionMode
let pqSup = PQSupportOn
pqSup' = pqSup `CR.pqSupportAnd` pqSupport
- vr <- chatVersionRange
- let chatV = vr `peerConnChatVersion` cReqChatVRange
+ cxt <- chatStoreCxt
+ let chatV = vr cxt `peerConnChatVersion` cReqChatVRange
(ct, conn, incognitoProfile) <- case contactId_ of
Nothing -> do
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
@@ -900,7 +900,7 @@ acceptContactRequest nm user@User {userId} UserContactRequest {agentInvitationId
createContactFromRequest db user userContactLinkId_ connId chatV cReqChatVRange cName profileId cp xContactId incognitoProfile subMode pqSup' False
pure (ct, conn, incognitoProfile)
Just contactId -> do
- ct <- withFastStore $ \db -> getContact db vr user contactId
+ ct <- withFastStore $ \db -> getContact db cxt user contactId
case contactConn ct of
Nothing -> do
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
@@ -926,15 +926,15 @@ acceptContactRequestAsync
incognitoProfile = do
subMode <- chatReadVar subscriptionMode
let profileToSend = userProfileDirect user (fromIncognitoProfile <$> incognitoProfile) (Just ct) True
- vr <- chatVersionRange
- let chatV = vr `peerConnChatVersion` cReqChatVRange
+ cxt <- chatStoreCxt
+ let chatV = vr cxt `peerConnChatVersion` cReqChatVRange
(cmdId, acId) <- agentAcceptContactAsync user True cReqInvId (XInfo profileToSend) subMode cReqPQSup chatV
currentTs <- liftIO getCurrentTime
withStore $ \db -> do
forM_ xContactId $ \xcId -> liftIO $ setContactAcceptedXContactId db ct xcId
Connection {connId} <- liftIO $ createAcceptedContactConn db user (Just uclId) contactId acId chatV cReqChatVRange cReqPQSup incognitoProfile subMode currentTs
liftIO $ setCommandConnId db user cmdId connId
- getContact db vr user contactId
+ getContact db cxt user contactId
acceptGroupJoinRequestAsync :: User -> Int64 -> GroupInfo -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> Maybe MemberId -> Maybe SharedMsgId -> GroupAcceptance -> GroupMemberRole -> Maybe IncognitoProfile -> Maybe MemberKey -> CM GroupMember
acceptGroupJoinRequestAsync
@@ -970,12 +970,12 @@ acceptGroupJoinRequestAsync
groupSize = Just currentMemCount
}
subMode <- chatReadVar subscriptionMode
- vr <- chatVersionRange
- let chatV = vr `peerConnChatVersion` cReqChatVRange
+ cxt <- chatStoreCxt
+ let chatV = vr cxt `peerConnChatVersion` cReqChatVRange
connIds <- agentAcceptContactAsync user True cReqInvId msg subMode PQSupportOff chatV
withStore $ \db -> do
liftIO $ createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode
- getGroupMemberById db vr user groupMemberId
+ getGroupMemberById db cxt user groupMemberId
acceptGroupJoinSendRejectAsync :: User -> Int64 -> GroupInfo -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> GroupRejectionReason -> CM GroupMember
acceptGroupJoinSendRejectAsync
@@ -1000,12 +1000,12 @@ acceptGroupJoinSendRejectAsync
rejectionReason
}
subMode <- chatReadVar subscriptionMode
- vr <- chatVersionRange
- let chatV = vr `peerConnChatVersion` cReqChatVRange
+ cxt <- chatStoreCxt
+ let chatV = vr cxt `peerConnChatVersion` cReqChatVRange
connIds <- agentAcceptContactAsync user False cReqInvId msg subMode PQSupportOff chatV
withStore $ \db -> do
liftIO $ createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode
- getGroupMemberById db vr user groupMemberId
+ getGroupMemberById db cxt user groupMemberId
acceptBusinessJoinRequestAsync :: User -> Int64 -> GroupInfo -> GroupMember -> UserContactRequest -> CM (GroupInfo, GroupMember)
acceptBusinessJoinRequestAsync
@@ -1014,7 +1014,7 @@ acceptBusinessJoinRequestAsync
gInfo@GroupInfo {membership = GroupMember {memberRole = userRole, memberId = userMemberId}}
clientMember@GroupMember {groupMemberId, memberId}
UserContactRequest {agentInvitationId = AgentInvId cReqInvId, cReqChatVRange, xContactId} = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
let userProfile@Profile {displayName, preferences} = fromLocalProfile $ profile' user
-- TODO [short links] take groupPreferences from group info
groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences
@@ -1033,7 +1033,7 @@ acceptBusinessJoinRequestAsync
groupSize = Just 1
}
subMode <- chatReadVar subscriptionMode
- let chatV = vr `peerConnChatVersion` cReqChatVRange
+ let chatV = vr cxt `peerConnChatVersion` cReqChatVRange
connIds <- agentAcceptContactAsync user True cReqInvId msg subMode PQSupportOff chatV
withStore' $ \db -> do
forM_ xContactId $ \xcId -> setBusinessChatAcceptedXContactId db gInfo xcId
@@ -1057,28 +1057,28 @@ acceptRelayJoinRequestAsync
-- TODO [channel web] derive RelayCapabilities from relay config (RelayWebOptions)
let msg = XGrpRelayAcpt relayLink defaultRelayCapabilities
subMode <- chatReadVar subscriptionMode
- vr <- chatVersionRange
- let chatV = vr `peerConnChatVersion` cReqChatVRange
+ cxt <- chatStoreCxt
+ let chatV = vr cxt `peerConnChatVersion` cReqChatVRange
connIds <- agentAcceptContactAsync user True cReqInvId msg subMode PQSupportOff chatV
withStore $ \db -> do
liftIO $ createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode
gInfo' <- liftIO $ updateRelayOwnStatusFromTo db gInfo RSInvited RSAccepted
- ownerMember' <- getGroupMemberById db vr user groupMemberId
+ ownerMember' <- getGroupMemberById db cxt user groupMemberId
pure (gInfo', ownerMember')
rejectRelayInvitationAsync
:: User
-> Int64
- -> VersionRangeChat
+ -> StoreCxt
-> GroupRelayInvitation
-> InvitationId
-> VersionRangeChat
-> Int64
-> RelayRejectionReason
-> CM ()
-rejectRelayInvitationAsync user uclId vr groupRelayInv invId reqChatVRange initialDelay reason = do
+rejectRelayInvitationAsync user uclId cxt groupRelayInv invId reqChatVRange initialDelay reason = do
(_gInfo, ownerMember) <- withStore $ \db ->
- createRelayRequestGroup db vr user groupRelayInv invId reqChatVRange initialDelay GSMemInvited RSRejected
+ createRelayRequestGroup db cxt user groupRelayInv invId reqChatVRange initialDelay GSMemInvited RSRejected
let GroupMember {groupMemberId} = ownerMember
msg = XGrpRelayReject reason
subMode <- chatReadVar subscriptionMode
@@ -1092,15 +1092,15 @@ businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile
businessGroupProfile Profile {displayName, fullName, shortDescr, image} groupPreferences =
GroupProfile {displayName, fullName, description = Nothing, shortDescr, image, publicGroup = Nothing, simplexName = Nothing, groupPreferences = Just groupPreferences, memberAdmission = Nothing}
-introduceToModerators :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
-introduceToModerators vr user gInfo@GroupInfo {groupId} m@GroupMember {memberRole, memberId} = do
+introduceToModerators :: StoreCxt -> User -> GroupInfo -> GroupMember -> CM ()
+introduceToModerators cxt user gInfo@GroupInfo {groupId} m@GroupMember {memberRole, memberId} = do
forM_ (memberConn m) $ \mConn -> do
let msg =
if maxVersion (memberChatVRange m) >= groupKnockingVersion
then XGrpLinkAcpt GAPendingReview memberRole memberId
else XMsgNew $ mcSimple (MCText pendingReviewMessage)
void $ sendDirectMemberMessage mConn msg groupId
- modMs <- withStore' $ \db -> getGroupModerators db vr user gInfo
+ modMs <- withStore' $ \db -> getGroupModerators db cxt user gInfo
let rcpModMs = filter shouldIntroduceToMod modMs
introduceMember user gInfo m rcpModMs (Just $ MSMember $ memberId' m)
where
@@ -1110,15 +1110,15 @@ introduceToModerators vr user gInfo@GroupInfo {groupId} m@GroupMember {memberRol
&& groupMemberId' mem /= groupMemberId' m
&& maxVersion (memberChatVRange mem) >= groupKnockingVersion
-introduceToAll :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
-introduceToAll vr user gInfo m = do
- (members, vector) <- withStore $ \db -> liftM2 (,) (liftIO $ getGroupMembers db vr user gInfo) (getMemberRelationsVector db m)
+introduceToAll :: StoreCxt -> User -> GroupInfo -> GroupMember -> CM ()
+introduceToAll cxt user gInfo m = do
+ (members, vector) <- withStore $ \db -> liftM2 (,) (liftIO $ getGroupMembers db cxt user gInfo) (getMemberRelationsVector db m)
let recipients = filter (shouldIntroduce m vector) members
introduceMember user gInfo m recipients Nothing
-introduceToRemaining :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
-introduceToRemaining vr user gInfo m = do
- (members, vector) <- withStore $ \db -> liftM2 (,) (liftIO $ getGroupMembers db vr user gInfo) (getMemberRelationsVector db m)
+introduceToRemaining :: StoreCxt -> User -> GroupInfo -> GroupMember -> CM ()
+introduceToRemaining cxt user gInfo m = do
+ (members, vector) <- withStore $ \db -> liftM2 (,) (liftIO $ getGroupMembers db cxt user gInfo) (getMemberRelationsVector db m)
let recipients = filter (shouldIntroduce m vector) members
introduceMember user gInfo m recipients Nothing
@@ -1172,10 +1172,10 @@ memberIntroEvt gInfo reMember =
-- Used in groups with relays to introduce moderators and above to a new member,
-- and to announce the new member to moderators and above.
-- This doesn't create introduction records in db, compared to above methods.
-introduceInChannel :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
+introduceInChannel :: StoreCxt -> User -> GroupInfo -> GroupMember -> CM ()
introduceInChannel _ _ _ GroupMember {activeConn = Nothing} = throwChatError $ CEInternalError "member connection not active"
-introduceInChannel vr user gInfo subscriber@GroupMember {activeConn = Just conn, indexInGroup = subscriberIdx} = do
- modMs <- withStore' $ \db -> getGroupModerators db vr user gInfo
+introduceInChannel cxt user gInfo subscriber@GroupMember {activeConn = Just conn, indexInGroup = subscriberIdx} = do
+ modMs <- withStore' $ \db -> getGroupModerators db cxt user gInfo
void $ sendGroupMessage' user gInfo modMs $ XGrpMemNew (memberInfo gInfo subscriber) Nothing
withStore' $ \db ->
setMemberVectorNewRelations db subscriber [(indexInGroup m, (IDSubjectIntroduced, MRIntroduced)) | m <- modMs]
@@ -1338,9 +1338,9 @@ setGroupLinkData' nm user gInfo =
setGroupLinkData :: NetworkRequestMode -> User -> GroupInfo -> GroupLink -> CM GroupLink
setGroupLinkData nm user gInfo gLink = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
(conn, groupRelays) <- withFastStore $ \db ->
- (,) <$> getGroupLinkConnection db vr user gInfo <*> liftIO (getConnectedGroupRelays db gInfo)
+ (,) <$> getGroupLinkConnection db cxt user gInfo <*> liftIO (getConnectedGroupRelays db gInfo)
let (userLinkData, crClientData) = groupLinkData gInfo gLink groupRelays
linkType = if useRelays' gInfo then CCTChannel else CCTGroup
sLnk <- shortenShortLink' . setShortLinkType_ linkType =<< withAgent (\a -> setConnShortLink a nm (aConnId conn) SCMContact userLinkData (Just crClientData))
@@ -1348,17 +1348,17 @@ setGroupLinkData nm user gInfo gLink = do
setGroupLinkDataAsync :: User -> GroupInfo -> GroupLink -> CM ()
setGroupLinkDataAsync user gInfo gLink = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
(conn, groupRelays) <- withStore $ \db ->
- (,) <$> getGroupLinkConnection db vr user gInfo <*> liftIO (getConnectedGroupRelays db gInfo)
+ (,) <$> getGroupLinkConnection db cxt user gInfo <*> liftIO (getConnectedGroupRelays db gInfo)
let (userLinkData, crClientData) = groupLinkData gInfo gLink groupRelays
setAgentConnShortLinkAsync user conn userLinkData (Just crClientData)
connectToRelayAsync :: User -> GroupInfo -> ShortLinkContact -> CM ()
connectToRelayAsync user gInfo relayLink = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
gVar <- asks random
- relayMember@GroupMember {activeConn} <- withFastStore $ \db -> getCreateRelayForMember db vr gVar user gInfo relayLink
+ relayMember@GroupMember {activeConn} <- withFastStore $ \db -> getCreateRelayForMember db cxt gVar user gInfo relayLink
case activeConn of
Just _ -> pure ()
Nothing -> do
@@ -1369,9 +1369,9 @@ connectToRelayAsync user gInfo relayLink = do
updatePublicGroupData :: User -> GroupInfo -> CM GroupInfo
updatePublicGroupData user gInfo
| useRelays' gInfo && memberRole' (membership gInfo) == GROwner = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
(gInfo', gLink) <- withStore $ \db -> do
- gInfo' <- updatePublicMemberCount db vr user gInfo
+ gInfo' <- updatePublicMemberCount db cxt user gInfo
gLink <- getGroupLink db user gInfo'
pure (gInfo', gLink)
setGroupLinkDataAsync user gInfo' gLink
@@ -1381,12 +1381,12 @@ updatePublicGroupData user gInfo
updateGroupFromLinkData :: User -> GroupInfo -> GroupShortLinkData -> CM (GroupInfo, Bool)
updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} GroupShortLinkData {groupProfile, publicGroupData}
| profileChanged || countChanged = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
withStore $ \db -> do
g <- if profileChanged then updateGroupProfile db user gInfo groupProfile else pure gInfo
g' <- case publicGroupData of
Just PublicGroupData {publicMemberCount} | countChanged ->
- setPublicMemberCount db vr user g publicMemberCount
+ setPublicMemberCount db cxt user g publicMemberCount
_ -> pure g
pure (g', profileChanged)
| otherwise = pure (gInfo, False)
@@ -1465,14 +1465,14 @@ shortenCreatedLink (CCLink cReq sLnk) = CCLink cReq <$> mapM shortenShortLink' s
deleteGroupLink' :: User -> GroupInfo -> CM ()
deleteGroupLink' user gInfo = do
- vr <- chatVersionRange
- conn <- withStore $ \db -> getGroupLinkConnection db vr user gInfo
+ cxt <- chatStoreCxt
+ conn <- withStore $ \db -> getGroupLinkConnection db cxt user gInfo
deleteGroupLink_ user gInfo conn
deleteGroupLinkIfExists :: User -> GroupInfo -> CM ()
deleteGroupLinkIfExists user gInfo = do
- vr <- chatVersionRange
- conn_ <- eitherToMaybe <$> withStore' (\db -> runExceptT $ getGroupLinkConnection db vr user gInfo)
+ cxt <- chatStoreCxt
+ conn_ <- eitherToMaybe <$> withStore' (\db -> runExceptT $ getGroupLinkConnection db cxt user gInfo)
mapM_ (deleteGroupLink_ user gInfo) conn_
deleteGroupLink_ :: User -> GroupInfo -> Connection -> CM ()
@@ -1507,16 +1507,16 @@ deleteTimedItem user (ChatRef cType chatId scope, itemId) deleteAt = do
ts <- liftIO getCurrentTime
liftIO $ threadDelay' $ diffToMicroseconds $ diffUTCTime deleteAt ts
lift waitChatStartedAndActivated
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
case cType of
CTDirect -> do
- (ct, ci) <- withStore $ \db -> (,) <$> getContact db vr user chatId <*> getDirectChatItem db user chatId itemId
+ (ct, ci) <- withStore $ \db -> (,) <$> getContact db cxt user chatId <*> getDirectChatItem db user chatId itemId
deletions <- deleteDirectCIs user ct [ci]
toView $ CEvtChatItemsDeleted user deletions True True
CTGroup -> do
- (gInfo, ci) <- withStore $ \db -> (,) <$> getGroupInfo db vr user chatId <*> getGroupChatItem db user chatId itemId
+ (gInfo, ci) <- withStore $ \db -> (,) <$> getGroupInfo db cxt user chatId <*> getGroupChatItem db user chatId itemId
deletedTs <- liftIO getCurrentTime
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
+ chatScopeInfo <- mapM (getChatScopeInfo cxt user) scope
deletions <- deleteGroupCIs user gInfo chatScopeInfo [ci] Nothing deletedTs
toView $ CEvtChatItemsDeleted user deletions True True
_ -> eToView $ ChatError $ CEInternalError "bad deleteTimedItem cType"
@@ -1633,25 +1633,25 @@ parseChatMessage conn s = do
errType = CEInvalidChatMessage conn Nothing (safeDecodeUtf8 s)
{-# INLINE parseChatMessage #-}
-getChatScopeInfo :: VersionRangeChat -> User -> GroupChatScope -> CM GroupChatScopeInfo
-getChatScopeInfo vr user = \case
+getChatScopeInfo :: StoreCxt -> User -> GroupChatScope -> CM GroupChatScopeInfo
+getChatScopeInfo cxt user = \case
GCSMemberSupport Nothing -> pure $ GCSIMemberSupport Nothing
GCSMemberSupport (Just gmId) -> do
- supportMem <- withFastStore $ \db -> getGroupMemberById db vr user gmId
+ supportMem <- withFastStore $ \db -> getGroupMemberById db cxt user gmId
pure $ GCSIMemberSupport (Just supportMem)
-getGroupRecipients :: VersionRangeChat -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> VersionChat -> CM [GroupMember]
-getGroupRecipients vr user gInfo@GroupInfo {membership} scopeInfo modsCompatVersion
+getGroupRecipients :: StoreCxt -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> VersionChat -> CM [GroupMember]
+getGroupRecipients cxt user gInfo@GroupInfo {membership} scopeInfo modsCompatVersion
| useRelays' gInfo && not (isRelay membership) = do
unless (memberCurrent membership && memberActive membership) $ throwChatError $ CECommandError "not current member"
- withFastStore' $ \db -> getGroupRelayMembers db vr user gInfo
+ withFastStore' $ \db -> getGroupRelayMembers db cxt user gInfo
| otherwise = case scopeInfo of
Nothing -> do
unless (memberCurrent membership && memberActive membership) $ throwChatError $ CECommandError "not current member"
- ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
+ ms <- withFastStore' $ \db -> getGroupMembers db cxt user gInfo
pure $ filter memberCurrent ms
Just (GCSIMemberSupport Nothing) -> do
- modMs <- withFastStore' $ \db -> getGroupModerators db vr user gInfo
+ modMs <- withFastStore' $ \db -> getGroupModerators db cxt user gInfo
let rcpModMs' = filter (\m -> compatible m && memberCurrent m) modMs
when (null rcpModMs') $ throwChatError $ CECommandError "no admins support this message"
pure rcpModMs'
@@ -1661,7 +1661,7 @@ getGroupRecipients vr user gInfo@GroupInfo {membership} scopeInfo modsCompatVers
if memberStatus supportMem == GSMemPendingApproval
then pure [supportMem]
else do
- modMs <- withFastStore' $ \db -> getGroupModerators db vr user gInfo
+ modMs <- withFastStore' $ \db -> getGroupModerators db cxt user gInfo
let rcpModMs' = filter (\m -> compatible m && memberCurrent m) modMs
pure $ [supportMem] <> rcpModMs'
where
@@ -1687,8 +1687,8 @@ mkGroupChatScope gInfo@GroupInfo {membership} m
| otherwise =
pure (gInfo, m, Nothing)
-mkGetMessageChatScope :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> MsgContent -> Maybe MsgScope -> CM (GroupInfo, GroupMember, Maybe GroupChatScopeInfo)
-mkGetMessageChatScope vr user gInfo@GroupInfo {membership} m mc msgScope_ =
+mkGetMessageChatScope :: StoreCxt -> User -> GroupInfo -> GroupMember -> MsgContent -> Maybe MsgScope -> CM (GroupInfo, GroupMember, Maybe GroupChatScopeInfo)
+mkGetMessageChatScope cxt user gInfo@GroupInfo {membership} m mc msgScope_ =
mkGroupChatScope gInfo m >>= \case
groupScope@(_gInfo', _m', Just _scopeInfo) -> pure groupScope
(_, _, Nothing)
@@ -1703,7 +1703,7 @@ mkGetMessageChatScope vr user gInfo@GroupInfo {membership} m mc msgScope_ =
(gInfo', scopeInfo) <- mkGroupSupportChatInfo gInfo
pure (gInfo', m, Just scopeInfo)
| otherwise -> do
- referredMember <- withStore $ \db -> getGroupMemberByMemberId db vr user gInfo mId
+ referredMember <- withStore $ \db -> getGroupMemberByMemberId db cxt user gInfo mId
-- TODO [knocking] return patched _referredMember'?
(_referredMember', scopeInfo) <- mkMemberSupportChatInfo referredMember
pure (gInfo, m, Just scopeInfo)
@@ -1817,8 +1817,8 @@ cancelSndFileTransfer user@User {userId} ft@SndFileTransfer {fileId, connId, fil
withStore' $ \db -> updateSndFileStatus db ft FSCancelled
when sendCancel $ case fileInline of
Just _ -> do
- vr <- chatVersionRange
- (sharedMsgId, conn) <- withStore $ \db -> (,) <$> getSharedMsgIdByFileId db userId fileId <*> getConnectionById db vr user connId
+ cxt <- chatStoreCxt
+ (sharedMsgId, conn) <- withStore $ \db -> (,) <$> getSharedMsgIdByFileId db userId fileId <*> getConnectionById db cxt user connId
void $ sendDirectMessage_ conn (BFileChunk sharedMsgId FileChunkCancel) (ConnectionId connId)
_ -> throwChatError $ CEException "cancelSndFileTransfer: cancelling file via a separate connection is deprecated"
@@ -2017,13 +2017,13 @@ batchSndMessagesJSON mode = batchMessages mode maxEncodedMsgLength . L.toList
encodeConnInfo :: MsgEncodingI e => ChatMsgEvent e -> CM ByteString
encodeConnInfo chatMsgEvent = do
- vr <- chatVersionRange
- encodeConnInfoPQ PQSupportOff (maxVersion vr) chatMsgEvent
+ cxt <- chatStoreCxt
+ encodeConnInfoPQ PQSupportOff (maxVersion (vr cxt)) chatMsgEvent
encodeConnInfoPQ :: MsgEncodingI e => PQSupport -> VersionChat -> ChatMsgEvent e -> CM ByteString
encodeConnInfoPQ pqSup v chatMsgEvent = do
- vr <- chatVersionRange
- let info = ChatMessage {chatVRange = vr, msgId = Nothing, chatMsgEvent}
+ cxt <- chatStoreCxt
+ let info = ChatMessage {chatVRange = vr cxt, msgId = Nothing, chatMsgEvent}
case encodeChatMessage maxEncodedInfoLength info of
ECMEncoded connInfo -> case pqSup of
PQSupportOn | v >= pqEncryptionCompressionVersion && B.length connInfo > maxCompressedInfoLength -> do
@@ -2337,8 +2337,8 @@ saveGroupRcvMsg user groupId authorMember conn@Connection {connId} agentMsgMeta
withStore (\db -> createNewMessageAndRcvMsgDelivery db (GroupId groupId) newMsg sharedMsgId_ rcvMsgDelivery $ Just amGroupMemId)
`catchAllErrors` \e -> case e of
ChatErrorStore (SEDuplicateGroupMessage _ _ _ (Just forwardedByGroupMemberId)) -> do
- vr <- chatVersionRange
- fm <- withStore $ \db -> getGroupMember db vr user groupId forwardedByGroupMemberId
+ cxt <- chatStoreCxt
+ fm <- withStore $ \db -> getGroupMember db cxt user groupId forwardedByGroupMemberId
forM_ (memberConn fm) $ \fmConn ->
void $ sendDirectMemberMessage fmConn (XGrpMemCon amMemId) groupId
throwError e
@@ -2358,8 +2358,8 @@ saveGroupFwdRcvMsg user gInfo@GroupInfo {groupId} forwardingMember refAuthorMemb
| useRelays' gInfo -> pure Nothing -- with chat relays, duplicates are expected
| otherwise -> case (authorGroupMemberId, forwardedByGroupMemberId) of
(Just authorGMId, Nothing) -> do
- vr <- chatVersionRange
- am@GroupMember {memberId = amMemberId} <- withStore $ \db -> getGroupMember db vr user groupId authorGMId
+ cxt <- chatStoreCxt
+ am@GroupMember {memberId = amMemberId} <- withStore $ \db -> getGroupMember db cxt user groupId authorGMId
if maybe False (\ref -> sameMemberId (memberId' ref) am) refAuthorMember_
then forM_ (memberConn forwardingMember) $ \fmConn ->
void $ sendDirectMemberMessage fmConn (XGrpMemCon amMemberId) groupId
@@ -2401,9 +2401,9 @@ saveSndChatItems ::
CM [Either ChatError (ChatItem c 'MDSnd)]
saveSndChatItems user cd showGroupAsSender itemsData itemTimed live = do
createdAt <- liftIO getCurrentTime
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
when (contactChatDeleted cd || any (\NewSndChatItemData {content} -> ciRequiresAttention content) (rights itemsData)) $
- void (withStore' $ \db -> updateChatTsStats db vr user cd createdAt Nothing)
+ void (withStore' $ \db -> updateChatTsStats db cxt user cd createdAt Nothing)
lift $ withStoreBatch (\db -> map (bindRight $ createItem db createdAt) itemsData)
where
createItem :: DB.Connection -> UTCTime -> NewSndChatItemData c -> IO (Either ChatError (ChatItem c 'MDSnd))
@@ -2429,14 +2429,14 @@ ciContentNoParse content = (content, (ciContentToText content, Nothing))
saveRcvChatItem' :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> UTCTime -> (CIContent 'MDRcv, (Text, Maybe MarkdownList)) -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> Map MemberName MsgMention -> CM (ChatItem c 'MDRcv, ChatInfo c)
saveRcvChatItem' user cd msg@RcvMessage {chatMsgEvent, msgSigned, forwardedByMember} sharedMsgId_ brokerTs (content, (t, ft_)) ciFile itemTimed live mentions = do
createdAt <- liftIO getCurrentTime
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
withStore' $ \db -> do
(mentions' :: Map MemberName CIMention, userMention) <- case toChatInfo cd of
GroupChat g@GroupInfo {membership} _ -> groupMentions db g membership
_ -> pure (M.empty, False)
cInfo' <-
if (ciRequiresAttention content || contactChatDeleted cd)
- then updateChatTsStats db vr user cd createdAt (memberChatStats userMention)
+ then updateChatTsStats db cxt user cd createdAt (memberChatStats userMention)
else pure $ toChatInfo cd
let showAsGroup = case cd of CDChannelRcv {} -> True; _ -> False
hasLink_ = ciContentHasLink content ft_
@@ -2729,13 +2729,13 @@ createChatItems ::
createChatItems user itemTs_ dirsCIContents = do
createdAt <- liftIO getCurrentTime
let itemTs = fromMaybe createdAt itemTs_
- vr <- chatVersionRange'
- void . withStoreBatch' $ \db -> map (updateChat db vr createdAt) dirsCIContents
+ cxt <- chatStoreCxt'
+ void . withStoreBatch' $ \db -> map (updateChat db cxt createdAt) dirsCIContents
withStoreBatch' $ \db -> concatMap (createACIs db itemTs createdAt) dirsCIContents
where
- updateChat :: DB.Connection -> VersionRangeChat -> UTCTime -> (ChatDirection c d, ShowGroupAsSender, [(CIContent d, Maybe SharedMsgId)]) -> IO ()
- updateChat db vr createdAt (cd, _, contents)
- | any (ciRequiresAttention . fst) contents || contactChatDeleted cd = void $ updateChatTsStats db vr user cd createdAt memberChatStats
+ updateChat :: DB.Connection -> StoreCxt -> UTCTime -> (ChatDirection c d, ShowGroupAsSender, [(CIContent d, Maybe SharedMsgId)]) -> IO ()
+ updateChat db cxt createdAt (cd, _, contents)
+ | any (ciRequiresAttention . fst) contents || contactChatDeleted cd = void $ updateChatTsStats db cxt user cd createdAt memberChatStats
| otherwise = pure ()
where
memberChatStats :: Maybe (Int, MemberAttention, Int)
@@ -2774,8 +2774,8 @@ createLocalChatItems ::
UTCTime ->
CM [ChatItem 'CTLocal 'MDSnd]
createLocalChatItems user cd itemsData createdAt = do
- vr <- chatVersionRange
- void $ withStore' $ \db -> updateChatTsStats db vr user cd createdAt Nothing
+ cxt <- chatStoreCxt
+ void $ withStore' $ \db -> updateChatTsStats db cxt user cd createdAt Nothing
(errs, items) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (createItem db) $ L.toList itemsData)
unless (null errs) $ toView $ CEvtChatErrors errs
pure items
@@ -2825,6 +2825,14 @@ waitChatStartedAndActivated = do
activated <- readTVar chatActivated
unless (isJust started && activated) retry
+chatStoreCxt :: CM StoreCxt
+chatStoreCxt = lift chatStoreCxt'
+{-# INLINE chatStoreCxt #-}
+
+chatStoreCxt' :: CM' StoreCxt
+chatStoreCxt' = mkStoreCxt <$> asks config
+{-# INLINE chatStoreCxt' #-}
+
chatVersionRange :: CM VersionRangeChat
chatVersionRange = lift chatVersionRange'
{-# INLINE chatVersionRange #-}
diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs
index 015830a850..975b5adf2b 100644
--- a/src/Simplex/Chat/Library/Subscriber.hs
+++ b/src/Simplex/Chat/Library/Subscriber.hs
@@ -117,10 +117,10 @@ processAgentMessage _ "" (ERR e) =
processAgentMessage corrId connId msg = do
lockEntity <- critical connId (withStore (`getChatLockEntity` AgentConnId connId))
withEntityLock "processAgentMessage" lockEntity $ do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
-- getUserByAConnId never throws logical errors, only SEDBBusyError can be thrown here
critical connId (withStore' (`getUserByAConnId` AgentConnId connId)) >>= \case
- Just user -> processAgentMessageConn vr user corrId connId msg `catchAllErrors` eToView
+ Just user -> processAgentMessageConn cxt user corrId connId msg `catchAllErrors` eToView
_ -> throwChatError $ CENoConnectionUser (AgentConnId connId)
-- CRITICAL error will be shown to the user as alert with restart button in Android/desktop apps.
@@ -182,27 +182,27 @@ processAgentMsgSndFile _corrId aFileId msg = do
process :: User -> FileTransferId -> CM ()
process user fileId = do
(ft@FileTransferMeta {xftpRedirectFor, cancelled}, sfts) <- withStore $ \db -> getSndFileTransfer db user fileId
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
unless cancelled $ case msg of
SFPROG sndProgress sndTotal -> do
let status = CIFSSndTransfer {sndProgress, sndTotal}
ci <- withStore $ \db -> do
liftIO $ updateCIFileStatus db user fileId status
- lookupChatItemByFileId db vr user fileId
+ lookupChatItemByFileId db cxt user fileId
toView $ CEvtSndFileProgressXFTP user ci ft sndProgress sndTotal
SFDONE sndDescr rfds -> do
withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr)
- ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId
+ ci <- withStore $ \db -> lookupChatItemByFileId db cxt user fileId
case ci of
Nothing -> do
lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId)
withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText rfds)
case rfds of
- [] -> sendFileError (FileErrOther "no receiver descriptions") "no receiver descriptions" vr ft
+ [] -> sendFileError (FileErrOther "no receiver descriptions") "no receiver descriptions" cxt ft
rfd : _ -> case [fd | fd@(FD.ValidFileDescription FD.FileDescription {chunks = [_]}) <- rfds] of
[] -> case xftpRedirectFor of
Nothing -> xftpSndFileRedirect user fileId rfd >>= toView . CEvtSndFileRedirectStartXFTP user ft
- Just _ -> sendFileError (FileErrOther "chaining redirects") "Prohibit chaining redirects" vr ft
+ Just _ -> sendFileError (FileErrOther "chaining redirects") "Prohibit chaining redirects" cxt ft
rfds' -> do
-- we have 1 chunk - use it as URI whether it is redirect or not
ft' <- maybe (pure ft) (\fId -> withStore $ \db -> getFileTransferMeta db user fId) xftpRedirectFor
@@ -235,13 +235,13 @@ processAgentMsgSndFile _corrId aFileId msg = do
sendFileDescriptions (GroupId groupId) rfdsMemberFTs' sharedMsgId
ci' <- withStore $ \db -> do
liftIO $ updateCIFileStatus db user fileId CIFSSndComplete
- getChatItemByFileId db vr user fileId
+ getChatItemByFileId db cxt user fileId
lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId)
toView $ CEvtSndFileCompleteXFTP user ci' ft
where
getRecipients
- | useRelays' g = withStore' $ \db -> getGroupRelayMembers db vr user g
- | otherwise = withStore' $ \db -> getGroupMembers db vr user g
+ | useRelays' g = withStore' $ \db -> getGroupRelayMembers db cxt user g
+ | otherwise = withStore' $ \db -> getGroupMembers db cxt user g
memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)]
memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts')
where
@@ -254,10 +254,10 @@ processAgentMsgSndFile _corrId aFileId msg = do
logWarn $ "Sent file warning: " <> err
ci <- withStore $ \db -> do
liftIO $ updateCIFileStatus db user fileId (CIFSSndWarning $ agentFileError e)
- lookupChatItemByFileId db vr user fileId
+ lookupChatItemByFileId db cxt user fileId
toView $ CEvtSndFileWarning user ci ft err
SFERR e ->
- sendFileError (agentFileError e) (tshow e) vr ft
+ sendFileError (agentFileError e) (tshow e) cxt ft
where
fileDescrText :: FilePartyI p => ValidFileDescription p -> T.Text
fileDescrText = safeDecodeUtf8 . strEncode
@@ -282,12 +282,12 @@ processAgentMsgSndFile _corrId aFileId msg = do
toMsgReq :: (Connection, (ConnOrGroupId, Maybe MsgSigning, ChatMsgEvent 'Json)) -> SndMessage -> ChatMsgReq
toMsgReq (conn, _) SndMessage {msgId, msgBody} =
(conn, MsgFlags {notification = hasNotification XMsgFileDescr_}, (vrValue msgBody, [msgId]))
- sendFileError :: FileError -> Text -> VersionRangeChat -> FileTransferMeta -> CM ()
- sendFileError ferr err vr ft = do
+ sendFileError :: FileError -> Text -> StoreCxt -> FileTransferMeta -> CM ()
+ sendFileError ferr err cxt ft = do
logError $ "Sent file error: " <> err
ci <- withStore $ \db -> do
liftIO $ updateFileCancelled db user fileId (CIFSSndError ferr)
- lookupChatItemByFileId db vr user fileId
+ lookupChatItemByFileId db cxt user fileId
lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId)
toView $ CEvtSndFileError user ci ft err
@@ -322,13 +322,13 @@ processAgentMsgRcvFile _corrId aFileId msg = do
process :: User -> FileTransferId -> CM ()
process user fileId = do
ft <- withStore $ \db -> getRcvFileTransfer db user fileId
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
unless (rcvFileCompleteOrCancelled ft) $ case msg of
RFPROG rcvProgress rcvTotal -> do
let status = CIFSRcvTransfer {rcvProgress, rcvTotal}
ci <- withStore $ \db -> do
liftIO $ updateCIFileStatus db user fileId status
- lookupChatItemByFileId db vr user fileId
+ lookupChatItemByFileId db cxt user fileId
toView $ CEvtRcvFileProgressXFTP user ci rcvProgress rcvTotal ft
RFDONE xftpPath ->
case liveRcvFileTransferPath ft of
@@ -340,13 +340,13 @@ processAgentMsgRcvFile _corrId aFileId msg = do
liftIO $ do
updateRcvFileStatus db fileId FSComplete
updateCIFileStatus db user fileId CIFSRcvComplete
- lookupChatItemByFileId db vr user fileId
+ lookupChatItemByFileId db cxt user fileId
agentXFTPDeleteRcvFile aFileId fileId
toView $ maybe (CEvtRcvStandaloneFileComplete user fsTargetPath ft) (CEvtRcvFileComplete user) ci_
RFWARN e -> do
ci <- withStore $ \db -> do
liftIO $ updateCIFileStatus db user fileId (CIFSRcvWarning $ agentFileError e)
- lookupChatItemByFileId db vr user fileId
+ lookupChatItemByFileId db cxt user fileId
toView $ CEvtRcvFileWarning user ci e ft
RFERR e
| e == FILE NOT_APPROVED -> do
@@ -357,21 +357,21 @@ processAgentMsgRcvFile _corrId aFileId msg = do
| otherwise -> do
aci_ <- withStore $ \db -> do
liftIO $ updateFileCancelled db user fileId (CIFSRcvError $ agentFileError e)
- lookupChatItemByFileId db vr user fileId
+ lookupChatItemByFileId db cxt user fileId
forM_ aci_ cleanupACIFile
agentXFTPDeleteRcvFile aFileId fileId
toView $ CEvtRcvFileError user aci_ e ft
type ShouldDeleteGroupConns = Bool
-processAgentMessageConn :: VersionRangeChat -> User -> ACorrId -> ConnId -> AEvent 'AEConn -> CM ()
-processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = do
+processAgentMessageConn :: StoreCxt -> User -> ACorrId -> ConnId -> AEvent 'AEConn -> CM ()
+processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = do
-- Missing connection/entity errors here will be sent to the view but not shown as CRITICAL alert,
-- as in this case no need to ACK message - we can't process messages for this connection anyway.
-- SEDBBusyError will be re-thrown as CRITICAL (via `critical`) as it indicates a transient lock/IO
-- condition that usually resolves after app restart. Other SEDBException flavours surface as
-- non-CRITICAL store errors.
- entity <- critical agentConnId $ withStore (\db -> getConnectionEntity db vr user $ AgentConnId agentConnId) >>= updateConnStatus
+ entity <- critical agentConnId $ withStore (\db -> getConnectionEntity db cxt user $ AgentConnId agentConnId) >>= updateConnStatus
case agentMessage of
END -> case entity of
RcvDirectMsgConnection _ (Just ct) -> toView $ CEvtContactAnotherClient user ct
@@ -574,7 +574,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- XGrpLinkInv here means we are connecting via business contact card, so we replace contact with group
(gInfo, host) <- withStore $ \db -> do
liftIO $ deleteContactCardKeepConn db connId ct
- createGroupInvitedViaLink db vr user conn'' glInv
+ createGroupInvitedViaLink db cxt user conn'' glInv
void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing (Just epochStart)
-- [incognito] send saved profile
incognitoProfile <- forM customUserProfileId $ \pId -> withStore (\db -> getProfileById db userId pId)
@@ -626,7 +626,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
when (connChatVersion < batchSend2Version) $ forM_ (autoReply $ addressSettings ucl) $ \mc -> sendAutoReply ct' mc Nothing -- old versions only
-- TODO REMOVE LEGACY vvv
forM_ gli_ $ \GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do
- groupInfo <- withStore $ \db -> getGroupInfo db vr user groupId
+ groupInfo <- withStore $ \db -> getGroupInfo db cxt user groupId
subMode <- chatReadVar subscriptionMode
groupConnIds <- createAgentConnectionAsync user CFCreateConnGrpInv True SCMInvitation subMode
gVar <- asks random
@@ -737,7 +737,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- [async agent commands] group link auto-accept continuation on receiving INV
CFCreateConnGrpInv -> do
(ct, groupLinkId) <- withStore $ \db -> do
- ct <- getContactViaMember db vr user m
+ ct <- getContactViaMember db cxt user m
liftIO $ setNewContactMemberConnRequest db user m cReq
liftIO $ (ct,) <$> getGroupLinkId db user gInfo
sendGrpInvitation ct m groupLinkId
@@ -805,7 +805,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
pgId = fmap (\PublicGroupProfile {publicGroupId} -> publicGroupId),
useRelays' gInfo == isJust rcvPG && pgId rcvPG == pgId curPG -> do
-- XGrpLinkInv here means we are connecting via prepared group, and we have to update user and host member records
- (gInfo', m') <- withStore $ \db -> updatePreparedUserAndHostMembersInvited db vr user gInfo m glInv
+ (gInfo', m') <- withStore $ \db -> updatePreparedUserAndHostMembersInvited db cxt user gInfo m glInv
-- [incognito] send saved profile
incognitoProfile <- forM customUserProfileId $ \pId -> withStore (\db -> getProfileById db userId pId)
let profileToSend = userProfileInGroup user gInfo (fromLocalProfile <$> incognitoProfile)
@@ -813,7 +813,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtGroupLinkConnecting user gInfo' m'
| otherwise -> messageError "x.grp.link.inv: publicGroupId mismatch"
XGrpLinkReject glRjct@GroupLinkRejection {rejectionReason} -> do
- (gInfo', m') <- withStore $ \db -> updatePreparedUserAndHostMembersRejected db vr user gInfo m glRjct
+ (gInfo', m') <- withStore $ \db -> updatePreparedUserAndHostMembersRejected db cxt user gInfo m glRjct
toView $ CEvtGroupLinkConnecting user gInfo' m'
toViewTE $ TEGroupLinkRejected user gInfo' rejectionReason
_ -> messageError "CONF from host member in prepared group must have x.grp.link.inv or x.grp.link.reject"
@@ -887,7 +887,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
where
firstConnectedHost
| useRelays' gInfo = do
- relayMems <- withStore' $ \db -> getGroupRelayMembers db vr user gInfo
+ relayMems <- withStore' $ \db -> getGroupRelayMembers db cxt user gInfo
let numConnected = length $ filter (\GroupMember {memberStatus = ms} -> ms == GSMemConnected) relayMems
pure $ numConnected == 1
| otherwise = pure True
@@ -917,13 +917,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
when (connChatVersion < batchSend2Version) $ getAutoReplyMsg >>= mapM_ (\mc -> sendGroupAutoReply mc Nothing)
if useRelays' gInfo''
then do
- introduceInChannel vr user gInfo'' m'
+ introduceInChannel cxt user gInfo'' m'
when (groupFeatureAllowed SGFHistory gInfo'') $ sendHistory user gInfo'' m'
else case mStatus of
GSMemPendingApproval -> pure ()
- GSMemPendingReview -> introduceToModerators vr user gInfo'' m'
+ GSMemPendingReview -> introduceToModerators cxt user gInfo'' m'
_ -> do
- introduceToAll vr user gInfo'' m'
+ introduceToAll cxt user gInfo'' m'
let memberIsCustomer = case businessChat gInfo'' of
Just BusinessChatInfo {chatType = BCCustomer, customerId} -> memberId' m' == customerId
_ -> False
@@ -946,12 +946,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
sendXGrpMemCon = \case
GCPreMember ->
forM_ (invitedByGroupMemberId membership) $ \hostId -> do
- host <- withStore $ \db -> getGroupMember db vr user groupId hostId
+ host <- withStore $ \db -> getGroupMember db cxt user groupId hostId
forM_ (memberConn host) $ \hostConn ->
void $ sendDirectMemberMessage hostConn (XGrpMemCon memberId) groupId
GCPostMember ->
forM_ (invitedByGroupMemberId m) $ \invitingMemberId -> do
- im <- withStore $ \db -> getGroupMember db vr user groupId invitingMemberId
+ im <- withStore $ \db -> getGroupMember db cxt user groupId invitingMemberId
forM_ (memberConn im) $ \imConn ->
void $ sendDirectMemberMessage imConn (XGrpMemCon memberId) groupId
_ -> messageWarning "sendXGrpMemCon: member category GCPreMember or GCPostMember is expected"
@@ -1211,7 +1211,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
(confId, m', relay) <- withStore $ \db -> do
confId <- getRelayConfId db m
liftIO $ updateGroupMemberStatus db userId m GSMemAccepted
- (m', relay) <- setRelayLinkAccepted db vr user m (MemberKey relayKey) relayProfile
+ (m', relay) <- setRelayLinkAccepted db cxt user m (MemberKey relayKey) relayProfile
pure (confId, m', relay)
allowAgentConnectionAsync user conn confId XOk
toView $ CEvtGroupRelayUpdated user gInfo m' relay
@@ -1299,7 +1299,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
FileChunkCancel ->
unless (rcvFileCompleteOrCancelled ft) $ do
cancelRcvFileTransfer user ft
- ci <- withStore $ \db -> getChatItemByFileId db vr user fileId
+ ci <- withStore $ \db -> getChatItemByFileId db cxt user fileId
toView $ CEvtRcvFileSndCancelled user ci ft
FileChunk {chunkNo, chunkBytes = chunk} -> do
case integrity of
@@ -1322,7 +1322,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
updateRcvFileStatus db fileId FSComplete
updateCIFileStatus db user fileId CIFSRcvComplete
deleteRcvFileChunks db ft
- getChatItemByFileId db vr user fileId
+ getChatItemByFileId db cxt user fileId
toView $ CEvtRcvFileComplete user ci
mapM_ (deleteAgentConnectionAsync . aConnId) conn_
RcvChunkDuplicate -> withAckMessage' "file msg" agentConnId meta $ pure ()
@@ -1347,7 +1347,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
case (ucGroupId_, auData) of
(Just groupId, UserContactLinkData UserContactData {relays = relayLinks}) -> do
(gInfo, gLink, relays, relaysChanged, newlyActiveLinks) <- withStore $ \db -> do
- gInfo <- getGroupInfo db vr user groupId
+ gInfo <- getGroupInfo db cxt user groupId
gLink <- getGroupLink db user gInfo
relays <- liftIO $ getGroupRelays db gInfo
(relays', changed, newlyActive) <- liftIO $ foldrM (updateRelay db) ([], False, []) relays
@@ -1360,7 +1360,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- dedicated subscriber count).
when (fromMaybe 0 publicMemberCount > 1) $
forM_ (L.nonEmpty newlyActiveLinks) $ \newlyActive -> do
- allRelayMembers <- withFastStore' $ \db -> getGroupRelayMembers db vr user gInfo
+ allRelayMembers <- withFastStore' $ \db -> getGroupRelayMembers db cxt user gInfo
let recipients =
filter
(\GroupMember {memberStatus, relayLink} ->
@@ -1410,7 +1410,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
AddressSettings {autoAccept} = addressSettings
isSimplexTeam = sameConnReqContact connReq adminContactReq
gVar <- asks random
- withStore (\db -> createOrUpdateContactRequest db gVar vr user uclId ucl isSimplexTeam invId chatVRange p xContactId_ welcomeMsgId_ requestMsg_ reqPQSup) >>= \case
+ withStore (\db -> createOrUpdateContactRequest db gVar cxt user uclId ucl isSimplexTeam invId chatVRange p xContactId_ welcomeMsgId_ requestMsg_ reqPQSup) >>= \case
RSAcceptedRequest _ucr re -> case re of
REContact ct ->
-- TODO [short links] update request msg
@@ -1542,7 +1542,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- ##### Group link join requests (don't create contact requests) #####
Just gli@GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do
-- TODO [short links] deduplicate request by xContactId?
- gInfo <- withStore $ \db -> getGroupInfo db vr user groupId
+ gInfo <- withStore $ \db -> getGroupInfo db cxt user groupId
if useRelays' gInfo
then messageWarning $ "processContactConnMessage (group " <> groupName' gInfo <> "): ignored direct join request from " <> displayName <> " (group uses relays)"
else do
@@ -1568,10 +1568,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
rejected <- withStore' $ \db -> isRelayGroupRejected db user groupLink
initialDelay <- asks $ initialInterval . relayRequestRetryInterval . config
if rejected
- then rejectRelayInvitationAsync user uclId vr groupRelayInv invId chatVRange initialDelay RRRRejoinRejected
+ then rejectRelayInvitationAsync user uclId cxt groupRelayInv invId chatVRange initialDelay RRRRejoinRejected
else do
(_gInfo, _ownerMember) <- withStore $ \db ->
- createRelayRequestGroup db vr user groupRelayInv invId chatVRange initialDelay GSMemAccepted RSInvited
+ createRelayRequestGroup db cxt user groupRelayInv invId chatVRange initialDelay GSMemAccepted RSInvited
lift $ void $ getRelayRequestWorker True
xGrpRelayTest :: InvitationId -> VersionRangeChat -> ByteString -> CM ()
xGrpRelayTest invId chatVRange challenge = do
@@ -1586,7 +1586,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let chatV = chatVR `peerConnChatVersion` chatVRange
(cmdId, acId) <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV
withStore $ \db -> do
- Connection {connId = testCId} <- createRelayTestConnection db vr user acId ConnAccepted chatV subMode
+ Connection {connId = testCId} <- createRelayTestConnection db cxt user acId ConnAccepted chatV subMode
liftIO $ setCommandConnId db user cmdId testCId
-- TODO [relays] owner, relays: TBC how to communicate member rejection rules from owner to relays
-- TODO [relays] relay: TBC communicate rejection when memberId already exists (currently checked in createJoiningMember)
@@ -1595,7 +1595,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
(_ucl, gLinkInfo_) <- withStore $ \db -> getUserContactLinkById db userId uclId
case gLinkInfo_ of
Just GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do
- gInfo <- withStore $ \db -> getGroupInfo db vr user groupId
+ gInfo <- withStore $ \db -> getGroupInfo db cxt user groupId
mem <- acceptGroupJoinRequestAsync user uclId gInfo invId chatVRange p Nothing (Just joiningMemberId) Nothing GAAccepted gLinkMemRole Nothing (Just joiningMemberKey)
(gInfo', mem', scopeInfo) <- mkGroupChatScope gInfo mem
createInternalChatItem user (CDGroupRcv gInfo' scopeInfo mem') (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing
@@ -1765,7 +1765,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- sendProbe -> sendProbeHashes (currently)
-- sendProbeHashes -> sendProbe (reversed - change order in code, may add delay)
sendProbe probe
- ms <- map COMGroupMember <$> withStore' (\db -> getMatchingMembers db vr user ct)
+ ms <- map COMGroupMember <$> withStore' (\db -> getMatchingMembers db cxt user ct)
sendProbeHashes ms probe probeId
else sendProbe . Probe =<< liftIO (encodedRandomBytes gVar 32)
where
@@ -1781,7 +1781,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
then do
(probe, probeId) <- withStore $ \db -> createSentProbe db gVar userId $ COMGroupMember m
sendProbe probe
- cs <- map COMContact <$> withStore' (\db -> getMatchingMemberContacts db vr user m)
+ cs <- map COMContact <$> withStore' (\db -> getMatchingMemberContacts db cxt user m)
sendProbeHashes cs probe probeId
else sendProbe . Probe =<< liftIO (encodedRandomBytes gVar 32)
where
@@ -1854,7 +1854,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
messageFileDescription Contact {contactId} sharedMsgId fileDescr = do
(fileId, aci) <- withStore $ \db -> do
fileId <- getFileIdBySharedMsgId db userId contactId sharedMsgId
- aci <- getChatItemByFileId db vr user fileId
+ aci <- getChatItemByFileId db cxt user fileId
pure (fileId, aci)
processFDMessage fileId aci fileDescr
@@ -1862,7 +1862,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
groupMessageFileDescription g@GroupInfo {groupId} m_ sharedMsgId fileDescr = do
(fileId, aci) <- withStore $ \db -> do
fileId <- getGroupFileIdBySharedMsgId db userId groupId sharedMsgId
- aci <- getChatItemByFileId db vr user fileId
+ aci <- getChatItemByFileId db cxt user fileId
pure (fileId, aci)
case aci of
AChatItem SCTGroup SMDRcv (GroupChat _g scopeInfo) ChatItem {chatDir}
@@ -2023,7 +2023,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
cci <- case itemMemberId of
Just itemMemberId' -> getGroupMemberCIBySharedMsgId db user g itemMemberId' sharedMsgId
Nothing -> getGroupChatItemBySharedMsgId db user g Nothing sharedMsgId
- scopeInfo <- getGroupChatScopeInfoForItem db vr user g (cChatItemId cci)
+ scopeInfo <- getGroupChatScopeInfoForItem db cxt user g (cChatItemId cci)
pure (cci, scopeInfo)
if ciReactionAllowed ci
then do
@@ -2061,13 +2061,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- no delivery task - message already forwarded by relay
pure Nothing
Just m@GroupMember {memberId} -> do
- (gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m content msgScope_
+ (gInfo', m', scopeInfo) <- mkGetMessageChatScope cxt user gInfo m content msgScope_
if blockedByAdmin m'
then createBlockedByAdmin gInfo' (Just m') scopeInfo $> Nothing
else case prohibitedGroupContent gInfo' m' scopeInfo content ft_ fInv_ False of
Just f -> rejected gInfo' (Just m') scopeInfo f $> Nothing
Nothing ->
- withStore' (\db -> getCIModeration db vr user gInfo' memberId sharedMsgId_) >>= \case
+ withStore' (\db -> getCIModeration db cxt user gInfo' memberId sharedMsgId_) >>= \case
Just ciModeration -> do
applyModeration gInfo' m' scopeInfo ciModeration
withStore' $ \db -> deleteCIModeration db gInfo' memberId sharedMsgId_
@@ -2157,7 +2157,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
else case m_ of
Just m -> do
let mentions' = if memberBlocked m then [] else mentions
- (gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m mc msgScope_
+ (gInfo', m', scopeInfo) <- mkGetMessageChatScope cxt user gInfo m mc msgScope_
pure (gInfo', CDGroupRcv gInfo' scopeInfo m', mentions', scopeInfo)
Nothing -> pure (gInfo, CDChannelRcv gInfo Nothing, mentions, Nothing)
case m_ >>= \m -> prohibitedGroupContent gInfo' m scopeInfo mc ft_ (Nothing :: Maybe String) False of
@@ -2188,7 +2188,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
else case m_ of
Just m -> getGroupMemberCIBySharedMsgId db user gInfo (memberId' m) sharedMsgId
Nothing -> getGroupChatItemBySharedMsgId db user gInfo Nothing sharedMsgId
- (cci,) <$> getGroupChatScopeInfoForItem db vr user gInfo (cChatItemId cci)
+ (cci,) <$> getGroupChatScopeInfoForItem db cxt user gInfo (cChatItemId cci)
case cci of
CChatItem SMDRcv ci@ChatItem {chatDir = CIGroupRcv m', meta = CIMeta {itemLive}, content = CIRcvMsgContent oldMC}
| isSender m' -> updateCI False ci scopeInfo oldMC itemLive (Just $ memberId' m')
@@ -2300,7 +2300,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| otherwise = a
delete :: CChatItem 'CTGroup -> Bool -> Maybe GroupMember -> CM (Maybe DeliveryTaskContext)
delete cci asGroup byGroupMember = do
- scopeInfo <- withStore $ \db -> getGroupChatScopeInfoForItem db vr user gInfo (cChatItemId cci)
+ scopeInfo <- withStore $ \db -> getGroupChatScopeInfoForItem db cxt user gInfo (cChatItemId cci)
let fullDelete
| asGroup = groupFeatureAllowed SGFFullDelete gInfo
| otherwise = maybe False (\m -> groupFeatureMemberAllowed SGFFullDelete m gInfo) m_
@@ -2368,14 +2368,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
(fileId,) <$> getRcvFileTransfer db user fileId
unless (rcvFileCompleteOrCancelled ft) $ do
cancelRcvFileTransfer user ft
- ci <- withStore $ \db -> getChatItemByFileId db vr user fileId
+ ci <- withStore $ \db -> getChatItemByFileId db cxt user fileId
toView $ CEvtRcvFileSndCancelled user ci ft
xFileAcptInv :: Contact -> SharedMsgId -> Maybe ConnReqInvitation -> String -> CM ()
xFileAcptInv ct sharedMsgId fileConnReq_ fName = do
(fileId, AChatItem _ _ _ ci) <- withStore $ \db -> do
fileId <- getDirectFileIdBySharedMsgId db user ct sharedMsgId
- (fileId,) <$> getChatItemByFileId db vr user fileId
+ (fileId,) <$> getChatItemByFileId db cxt user fileId
assertSMPAcceptNotProhibited ci
ft@FileTransferMeta {fileName, fileSize, fileInline, cancelled} <- withStore (\db -> getFileTransferMeta db user fileId)
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
@@ -2384,7 +2384,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- receiving inline
Nothing -> do
event <- withStore $ \db -> do
- ci' <- updateDirectCIFileStatus db vr user fileId $ CIFSSndTransfer 0 1
+ ci' <- updateDirectCIFileStatus db cxt user fileId $ CIFSSndTransfer 0 1
sft <- createSndDirectInlineFT db ct ft
pure $ CEvtSndFileStart user ci' sft
toView event
@@ -2412,7 +2412,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
forM_ sft_ $ \sft@SndFileTransfer {fileId} -> do
ci@(AChatItem _ _ _ ChatItem {file}) <- withStore $ \db -> do
liftIO $ updateSndFileStatus db sft FSComplete
- updateDirectCIFileStatus db vr user fileId CIFSSndComplete
+ updateDirectCIFileStatus db cxt user fileId CIFSSndComplete
case file of
Just CIFile {fileProtocol = FPXFTP} -> do
ft <- withStore $ \db -> getFileTransferMeta db user fileId
@@ -2450,7 +2450,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
xFileCancelGroup g@GroupInfo {groupId} m_ sharedMsgId = do
(fileId, aci) <- withStore $ \db -> do
fileId <- getGroupFileIdBySharedMsgId db userId groupId sharedMsgId
- (fileId,) <$> getChatItemByFileId db vr user fileId
+ (fileId,) <$> getChatItemByFileId db cxt user fileId
case aci of
AChatItem SCTGroup SMDRcv (GroupChat _g scopeInfo) ChatItem {chatDir}
| validSender m_ chatDir -> do
@@ -2466,7 +2466,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
xFileAcptInvGroup GroupInfo {groupId} m@GroupMember {activeConn} sharedMsgId fileConnReq_ fName = do
(fileId, AChatItem _ _ _ ci) <- withStore $ \db -> do
fileId <- getGroupFileIdBySharedMsgId db userId groupId sharedMsgId
- (fileId,) <$> getChatItemByFileId db vr user fileId
+ (fileId,) <$> getChatItemByFileId db cxt user fileId
assertSMPAcceptNotProhibited ci
-- TODO check that it's not already accepted
ft@FileTransferMeta {fileName, fileSize, fileInline, cancelled} <- withStore (\db -> getFileTransferMeta db user fileId)
@@ -2475,7 +2475,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
(Nothing, Just conn) -> do
-- receiving inline
event <- withStore $ \db -> do
- ci' <- updateDirectCIFileStatus db vr user fileId $ CIFSSndTransfer 0 1
+ ci' <- updateDirectCIFileStatus db cxt user fileId $ CIFSSndTransfer 0 1
sft <- liftIO $ createSndGroupInlineFT db m conn ft
pure $ CEvtSndFileStart user ci' sft
toView event
@@ -2501,7 +2501,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
when (fromRole < GRAdmin || fromRole < memRole) $ throwChatError (CEGroupContactRole c)
when (fromMemId == memId) $ throwChatError CEGroupDuplicateMemberId
-- [incognito] if direct connection with host is incognito, create membership using the same incognito profile
- (gInfo@GroupInfo {groupId, localDisplayName, groupProfile, membership}, hostId) <- withStore $ \db -> createGroupInvitation db vr user ct inv customUserProfileId
+ (gInfo@GroupInfo {groupId, localDisplayName, groupProfile, membership}, hostId) <- withStore $ \db -> createGroupInvitation db cxt user ct inv customUserProfileId
void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing (Just epochStart)
let GroupMember {groupMemberId, memberId = membershipMemId} = membership
if sameGroupLinkId groupLinkId groupLinkId'
@@ -2542,7 +2542,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
then do
(ct', contactConns) <- withStore' $ \db -> do
ct' <- updateContactStatus db user c CSDeleted
- (ct',) <$> getContactConnections db vr userId ct'
+ (ct',) <$> getContactConnections db cxt userId ct'
deleteAgentConnectionsAsync $ map aConnId contactConns
forM_ contactConns $ \conn -> withStore' $ \db -> updateConnectionStatus db conn ConnDeleted
activeConn' <- forM (contactConn ct') $ \conn -> pure conn {connStatus = ConnDeleted}
@@ -2551,7 +2551,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
toView $ CEvtContactDeletedByContact user ct''
else do
- contactConns <- withStore' $ \db -> getContactConnections db vr userId c
+ contactConns <- withStore' $ \db -> getContactConnections db cxt userId c
deleteAgentConnectionsAsync $ map aConnId contactConns
withStore $ \db -> deleteContact db user c
where
@@ -2629,7 +2629,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
messageError "x.grp.link.acpt with insufficient member permissions"
| sameMemberId memberId membership = processUserAccepted
| otherwise =
- withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memberId) >>= \case
+ withStore' (\db -> runExceptT $ getGroupMemberByMemberId db cxt user gInfo memberId) >>= \case
Left _ -> messageError "x.grp.link.acpt error: referenced member does not exist"
Right referencedMember -> do
(referencedMember', gInfo') <- withStore' $ \db -> do
@@ -2673,7 +2673,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
GAPendingApproval ->
messageWarning "x.grp.link.acpt: unexpected group acceptance - pending approval"
introduceToRemainingMembers acceptedMember = do
- introduceToRemaining vr user gInfo acceptedMember
+ introduceToRemaining cxt user gInfo acceptedMember
when (groupFeatureAllowed SGFHistory gInfo) $ sendHistory user gInfo acceptedMember
maybeCreateGroupDescrLocal :: GroupInfo -> GroupMember -> CM ()
@@ -2697,7 +2697,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtGroupMemberUpdated user gInfo m m'
pure m'
Just mContactId -> do
- mCt <- withStore $ \db -> getContact db vr user mContactId
+ mCt <- withStore $ \db -> getContact db cxt user mContactId
if canUpdateProfile mCt
then do
(m', ct', displaced_) <- withStore $ \db -> updateContactMemberProfileWithConflict db user m mCt p'
@@ -2748,7 +2748,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
contactMerge <- readTVarIO =<< asks contactMergeEnabled
-- [incognito] unless connected incognito
when (contactMerge && not (contactOrMemberIncognito cgm2)) $ do
- cgm1s <- withStore' $ \db -> matchReceivedProbe db vr user cgm2 probe
+ cgm1s <- withStore' $ \db -> matchReceivedProbe db cxt user cgm2 probe
let cgm1s' = filter (not . contactOrMemberIncognito) cgm1s
probeMatches cgm1s' cgm2
where
@@ -2764,7 +2764,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
contactMerge <- readTVarIO =<< asks contactMergeEnabled
-- [incognito] unless connected incognito
when (contactMerge && not (contactOrMemberIncognito cgm1)) $ do
- cgm2Probe_ <- withStore' $ \db -> matchReceivedProbeHash db vr user cgm1 probeHash
+ cgm2Probe_ <- withStore' $ \db -> matchReceivedProbeHash db cxt user cgm1 probeHash
forM_ cgm2Probe_ $ \(cgm2, probe) ->
unless (contactOrMemberIncognito cgm2) . void $
probeMatch cgm1 cgm2 probe
@@ -2794,7 +2794,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
xInfoProbeOk :: ContactOrMember -> Probe -> CM ()
xInfoProbeOk cgm1 probe = do
- cgm2 <- withStore' $ \db -> matchSentProbe db vr user cgm1 probe
+ cgm2 <- withStore' $ \db -> matchSentProbe db cxt user cgm1 probe
case cgm1 of
COMContact c1 ->
case cgm2 of
@@ -2943,14 +2943,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
associateMemberWithContact c1 m2@GroupMember {groupId} = do
g <- withStore $ \db -> do
liftIO $ associateMemberWithContactRecord db user c1 m2
- getGroupInfo db vr user groupId
+ getGroupInfo db cxt user groupId
toView $ CEvtContactAndMemberAssociated user c1 g m2 c1
pure c1
associateContactWithMember :: GroupMember -> Contact -> CM Contact
associateContactWithMember m1@GroupMember {groupId} c2 = do
(c2', g) <- withStore $ \db ->
- liftM2 (,) (associateContactWithMemberRecord db vr user m1 c2) (getGroupInfo db vr user groupId)
+ liftM2 (,) (associateContactWithMemberRecord db cxt user m1 c2) (getGroupInfo db cxt user groupId)
toView $ CEvtContactAndMemberAssociated user c2 g m1 c2'
pure c2'
@@ -2966,17 +2966,17 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- the source of truth.
let Connection {simplexName} = conn'
Profile {simplexName = pSimplexName} = p
- (ct, displaced_) <- withStore $ \db -> createDirectContact db vr user conn' p simplexName
+ (ct, displaced_) <- withStore $ \db -> createDirectContact db cxt user conn' p simplexName
let Contact {localDisplayName = newLDN} = ct
surfaceSimplexNameConflict user pSimplexName displaced_ SNCEContact newLDN
toView $ CEvtContactConnecting user ct
pure (conn', Nothing)
XGrpLinkInv glInv -> do
- (gInfo, host) <- withStore $ \db -> createGroupInvitedViaLink db vr user conn' glInv
+ (gInfo, host) <- withStore $ \db -> createGroupInvitedViaLink db cxt user conn' glInv
toView $ CEvtGroupLinkConnecting user gInfo host
pure (conn', Just gInfo)
XGrpLinkReject glRjct@GroupLinkRejection {rejectionReason} -> do
- (gInfo, host) <- withStore $ \db -> createGroupRejectedViaLink db vr user conn' glRjct
+ (gInfo, host) <- withStore $ \db -> createGroupRejectedViaLink db cxt user conn' glRjct
toView $ CEvtGroupLinkConnecting user gInfo host
toViewTE $ TEGroupLinkRejected user gInfo rejectionReason
pure (conn', Just gInfo)
@@ -2991,10 +2991,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
if sameMemberId memId (membership gInfo)
then pure Nothing
else do
- withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case
+ withStore' (\db -> runExceptT $ getGroupMemberByMemberId db cxt user gInfo memId) >>= \case
Right unknownMember@GroupMember {memberStatus = GSMemUnknown} -> do
(updatedMember, gInfo') <- withStore $ \db -> do
- updatedMember <- updateUnknownMemberAnnounced db vr user m unknownMember memInfo initialStatus
+ updatedMember <- updateUnknownMemberAnnounced db cxt user m unknownMember memInfo initialStatus
gInfo' <-
if memberPending updatedMember
then liftIO $ increaseGroupMembersRequireAttention db user gInfo
@@ -3047,10 +3047,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _ _) memRestrictions = do
case memberCategory m of
GCHostMember ->
- withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case
+ withStore' (\db -> runExceptT $ getGroupMemberByMemberId db cxt user gInfo memId) >>= \case
Right existingMember
| useRelays' gInfo -> do
- updatedMember <- withStore $ \db -> updatePreparedChannelMember db vr user existingMember memInfo
+ updatedMember <- withStore $ \db -> updatePreparedChannelMember db cxt user existingMember memInfo
toView $ CEvtGroupMemberUpdated user gInfo existingMember updatedMember
| otherwise ->
messageError "x.grp.mem.intro ignored: member already exists"
@@ -3071,7 +3071,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
subMode <- chatReadVar subscriptionMode
-- [async agent commands] commands should be asynchronous, continuation is to send XGrpMemInv - have to remember one has completed and process on second
groupConnIds <- createConn subMode
- let chatV = maybe (minVersion vr) (\peerVR -> vr `peerConnChatVersion` fromChatVRange peerVR) memChatVRange
+ let chatV = maybe (minVersion (vr cxt)) (\peerVR -> vr cxt `peerConnChatVersion` fromChatVRange peerVR) memChatVRange
void $ withStore $ \db -> do
reMember <- createIntroReMember db user gInfo memInfo memRestrictions
createIntroReMemberConn db user m reMember chatV memInfo groupConnIds subMode
@@ -3082,7 +3082,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
sendXGrpMemInv :: Int64 -> Maybe ConnReqInvitation -> XGrpMemIntroCont -> CM ()
sendXGrpMemInv hostConnId directConnReq XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} = do
- hostConn <- withStore $ \db -> getConnectionById db vr user hostConnId
+ hostConn <- withStore $ \db -> getConnectionById db cxt user hostConnId
let msg = XGrpMemInv memberId IntroInvitation {groupConnReq, directConnReq}
void $ sendDirectMemberMessage hostConn msg groupId
withStore' $ \db -> updateGroupMemberStatusById db userId groupMemberId GSMemIntroInvited
@@ -3091,7 +3091,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
xGrpMemInv gInfo m memId introInv = do
case memberCategory m of
GCInviteeMember ->
- withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case
+ withStore' (\db -> runExceptT $ getGroupMemberByMemberId db cxt user gInfo memId) >>= \case
Left _ -> messageError "x.grp.mem.inv error: referenced member does not exist"
Right reMember -> sendGroupMemberMessage gInfo reMember $ XGrpMemFwd (memberInfo gInfo m) introInv
_ -> messageError "x.grp.mem.inv can be only sent by invitee member"
@@ -3102,7 +3102,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
checkHostRole m memRole
toMember <- withStore $ \db -> do
toMember <-
- getGroupMemberByMemberId db vr user gInfo memId
+ getGroupMemberByMemberId db cxt user gInfo memId
-- TODO if the missed messages are correctly sent as soon as there is connection before anything else is sent
-- the situation when member does not exist is an error
-- member receiving x.grp.mem.fwd should have also received x.grp.mem.new prior to that.
@@ -3126,7 +3126,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
directConnIds <- forM directConnReq $ \dcr -> joinAgentConnectionAsync user Nothing True dcr dm subMode
let customUserProfileId = localProfileId <$> incognitoMembershipProfile gInfo
mcvr = maybe chatInitialVRange fromChatVRange memChatVRange
- chatV = vr `peerConnChatVersion` mcvr
+ chatV = vr cxt `peerConnChatVersion` mcvr
withStore' $ \db -> createIntroToMemberContact db user m toMember chatV mcvr groupConnIds directConnIds customUserProfileId subMode
xGrpMemRole :: GroupInfo -> GroupMember -> MemberId -> GroupMemberRole -> RcvMessage -> UTCTime -> CM (Maybe DeliveryJobScope)
@@ -3135,7 +3135,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let gInfo' = gInfo {membership = membership {memberRole = memRole}}
in changeMemberRole gInfo' membership $ RGEUserRole memRole
| otherwise =
- withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case
+ withStore' (\db -> runExceptT $ getGroupMemberByMemberId db cxt user gInfo memId) >>= \case
Right member -> changeMemberRole gInfo member $ RGEMemberRole (groupMemberId' member) (fromLocalProfile $ memberProfile member) memRole
Left _ -> messageError "x.grp.mem.role with unknown member ID" $> Nothing
where
@@ -3166,7 +3166,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| membershipMemId == memId = pure Nothing -- ignore - XGrpMemRestrict can be sent to restricted member for efficiency
| otherwise = do
unknownRole <- unknownMemberRole gInfo
- withStore (\db -> getCreateUnknownGMByMemberId db vr user gInfo memId "" unknownRole True) >>= \case
+ withStore (\db -> getCreateUnknownGMByMemberId db cxt user gInfo memId "" unknownRole True) >>= \case
Nothing -> messageError "x.grp.mem.restrict: no member" $> Nothing -- shouldn't happen
Just (bm, unknown) -> do
let GroupMember {groupMemberId = bmId, memberRole, blockedByAdmin, memberProfile = bmp} = bm
@@ -3190,7 +3190,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
xGrpMemCon :: GroupInfo -> GroupMember -> MemberId -> CM ()
xGrpMemCon gInfo sendingMem memId = do
- refMem <- withStore $ \db -> getGroupMemberByMemberId db vr user gInfo memId
+ refMem <- withStore $ \db -> getGroupMemberByMemberId db cxt user gInfo memId
-- Updating vectors in separate transactions to avoid deadlocks.
withStore $ \db -> setMemberVectorRelationConnected db sendingMem refMem MRSubjectConnected
withStore $ \db -> setMemberVectorRelationConnected db refMem sendingMem MRReferencedConnected
@@ -3212,7 +3212,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtDeletedMemberUser user gInfo {membership = membership'} m withMessages msgSigned
pure $ Just DJSGroup {jobSpec = DJRelayRemoved}
else
- withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case
+ withStore' (\db -> runExceptT $ getGroupMemberByMemberId db cxt user gInfo memId) >>= \case
Left _ -> do
messageError "x.grp.mem.del with unknown member ID"
pure $ Just DJSGroup {jobSpec = DJDeliveryJob {includePending = True}}
@@ -3365,7 +3365,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
case memberContactId of
Nothing -> createNewContact subMode
Just mContactId -> do
- mCt <- withStore $ \db -> getContact db vr user mContactId
+ mCt <- withStore $ \db -> getContact db cxt user mContactId
let Contact {activeConn, contactGrpInvSent} = mCt
forM_ activeConn $ \Connection {connId} ->
if contactGrpInvSent
@@ -3392,7 +3392,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
mCt' <- withStore $ \db -> do
updateMemberContactInvited db user mCt groupDirectInv
void $ liftIO $ createMemberContactConn db user acId (Just cmdId) g mConn ConnJoined mContactId subMode
- getContact db vr user mContactId
+ getContact db cxt user mContactId
securityCodeChanged mCt'
createItems mCt' m
| otherwise = do
@@ -3400,7 +3400,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
mCt' <- withStore $ \db -> do
updateMemberContactInvited db user mCt groupDirectInv
void $ liftIO $ createMemberContactConn db user acId Nothing g mConn ConnPrepared mContactId subMode
- getContact db vr user mContactId
+ getContact db cxt user mContactId
securityCodeChanged mCt'
createInternalChatItem user (CDDirectRcv mCt') (CIRcvDirectEvent $ RDEGroupInvLinkReceived gp) Nothing
createItems mCt' m
@@ -3411,7 +3411,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
(mCt, m') <- withStore $ \db -> do
(mContactId, m') <- liftIO $ createMemberContactInvited db user g m groupDirectInv
void $ liftIO $ createMemberContactConn db user acId (Just cmdId) g mConn ConnJoined mContactId subMode
- mCt <- getContact db vr user mContactId
+ mCt <- getContact db cxt user mContactId
pure (mCt, m')
createInternalChatItem user (CDDirectSnd mCt) CIChatBanner (Just epochStart)
createItems mCt m'
@@ -3420,7 +3420,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
(mCt, m') <- withStore $ \db -> do
(mContactId, m') <- liftIO $ createMemberContactInvited db user g m groupDirectInv
void $ liftIO $ createMemberContactConn db user acId Nothing g mConn ConnPrepared mContactId subMode
- mCt <- getContact db vr user mContactId
+ mCt <- getContact db cxt user mContactId
pure (mCt, m')
createInternalChatItem user (CDDirectSnd mCt) CIChatBanner (Just epochStart)
createInternalChatItem user (CDDirectRcv mCt) (CIRcvDirectEvent $ RDEGroupInvLinkReceived gp) Nothing
@@ -3451,7 +3451,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
FwdMember memberId memberName -> do
unknownRole <- unknownMemberRole gInfo
let allowCreate = toCMEventTag chatMsgEvent /= XGrpLeave_
- withStore (\db -> getCreateUnknownGMByMemberId db vr user gInfo memberId memberName unknownRole allowCreate) >>= \case
+ withStore (\db -> getCreateUnknownGMByMemberId db cxt user gInfo memberId memberName unknownRole allowCreate) >>= \case
Just (author, unknown)
| memberRemoved author ->
logInfo $ "x.grp.msg.forward: ignoring content from removed member, group " <> tshow (groupId' gInfo) <> ", member " <> safeDecodeUtf8 (strEncode memberId) <> ", event " <> tshow (toCMEventTag chatMsgEvent)
@@ -3507,8 +3507,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
Just sm@SignedMsg {chatBinding, signatures, signedBody}
| GroupMember {memberPubKey = Just pubKey, memberId} <- member ->
case chatBinding of
- CBGroup | Just GroupKeys {publicGroupId} <- groupKeys gInfo ->
- let prefix = smpEncode chatBinding <> smpEncode (publicGroupId, memberId)
+ CBGroup ->
+ let prefix = smpEncode chatBinding <> bindingData
+ bindingData = case groupKeys gInfo of
+ Just GroupKeys {publicGroupId} -> smpEncode (publicGroupId, memberId)
+ Nothing -> smpEncode (memberId, pubKey) -- forward compatibility for verifying signed messages in p2p groups
in signed MSSVerified <$ guard (all (\(MsgSignature KRMember sig) -> C.verify (C.APublicVerifyKey C.SEd25519 pubKey) sig (prefix <> signedBody)) signatures)
_ -> signed MSSSignedNoKey <$ guard signatureOptional
| otherwise -> signed MSSSignedNoKey <$ guard (signatureOptional || unverifiedAllowed membership member tag)
@@ -3581,7 +3584,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- SENT and RCVD events are received for messages that may be batched in single scope,
-- so we can look up scope of first item
scopeInfo <- case cis of
- (ci : _) -> getGroupChatScopeInfoForItem db vr user gInfo (chatItemId' ci)
+ (ci : _) -> getGroupChatScopeInfoForItem db cxt user gInfo (chatItemId' ci)
_ -> pure Nothing
pure $ map (gItem scopeInfo) cis
unless (null acis) $ toView $ CEvtChatItemsStatusesUpdated user acis
@@ -3605,14 +3608,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
deleteGroupConnections :: User -> GroupInfo -> Bool -> CM ()
deleteGroupConnections user gInfo@GroupInfo {membership} waitDelivery = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
-- member records are not deleted to keep history
- members <- getMembers vr
+ members <- getMembers cxt
deleteMembersConnections' user members waitDelivery
where
- getMembers vr
- | useRelays' gInfo, not (isRelay membership) = withStore' $ \db -> getGroupRelayMembers db vr user gInfo
- | otherwise = withStore' $ \db -> getGroupMembers db vr user gInfo
+ getMembers cxt
+ | useRelays' gInfo, not (isRelay membership) = withStore' $ \db -> getGroupRelayMembers db cxt user gInfo
+ | otherwise = withStore' $ \db -> getGroupMembers db cxt user gInfo
startDeliveryTaskWorkers :: CM ()
startDeliveryTaskWorkers = do
@@ -3632,20 +3635,20 @@ getDeliveryTaskWorker hasWork deliveryKey = do
runDeliveryTaskWorker :: AgentClient -> DeliveryWorkerKey -> Worker -> CM ()
runDeliveryTaskWorker a deliveryKey Worker {doWork} = do
delay <- asks $ deliveryWorkerDelay . config
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
-- TODO [relays] in future may be required to read groupInfo and user on each iteration for up to date state
-- TODO - same for delivery jobs (runDeliveryJobWorker)
gInfo <- withStore $ \db -> do
user <- getUserByGroupId db groupId
- getGroupInfo db vr user groupId
+ getGroupInfo db cxt user groupId
forever $ do
unless (delay == 0) $ liftIO $ threadDelay' delay
lift $ waitForWork doWork
- runDeliveryTaskOperation vr gInfo
+ runDeliveryTaskOperation cxt gInfo
where
(groupId, workerScope) = deliveryKey
- runDeliveryTaskOperation :: VersionRangeChat -> GroupInfo -> CM ()
- runDeliveryTaskOperation vr gInfo = do
+ runDeliveryTaskOperation :: StoreCxt -> GroupInfo -> CM ()
+ runDeliveryTaskOperation cxt gInfo = do
withWork_ a doWork (withStore' $ \db -> getNextDeliveryTask db deliveryKey) $ \task ->
processDeliveryTask task
`catchAllErrors` \e -> do
@@ -3661,7 +3664,7 @@ runDeliveryTaskWorker a deliveryKey Worker {doWork} = do
withStore' $ \db -> setDeliveryTaskErrStatus db (deliveryTaskId task) "relay inactive"
| otherwise ->
withWorkItems a doWork (withStore' $ \db -> getNextDeliveryTasks db gInfo task) $ \nextTasks -> do
- let (body, acceptedTasks, largeTasks) = batchDeliveryTasks1 vr maxEncodedMsgLength nextTasks
+ let (body, acceptedTasks, largeTasks) = batchDeliveryTasks1 (vr cxt) maxEncodedMsgLength nextTasks
senderGMIds = S.toList . S.fromList $ map (\MessageDeliveryTask {senderGMId} -> senderGMId) acceptedTasks
withStore' $ \db -> do
createMsgDeliveryJob db gInfo jobScope senderGMIds body
@@ -3720,19 +3723,19 @@ encodeMemberNew vr gInfo member = case encodeChatMessage maxBatchElementSize cha
runDeliveryJobWorker :: AgentClient -> DeliveryWorkerKey -> Worker -> CM ()
runDeliveryJobWorker a deliveryKey Worker {doWork} = do
delay <- asks $ deliveryWorkerDelay . config
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
(user, gInfo) <- withStore $ \db -> do
user <- getUserByGroupId db groupId
- gInfo <- getGroupInfo db vr user groupId
+ gInfo <- getGroupInfo db cxt user groupId
pure (user, gInfo)
forever $ do
unless (delay == 0) $ liftIO $ threadDelay' delay
lift $ waitForWork doWork
- runDeliveryJobOperation vr user gInfo
+ runDeliveryJobOperation cxt user gInfo
where
(groupId, workerScope) = deliveryKey
- runDeliveryJobOperation :: VersionRangeChat -> User -> GroupInfo -> CM ()
- runDeliveryJobOperation vr user gInfo = do
+ runDeliveryJobOperation :: StoreCxt -> User -> GroupInfo -> CM ()
+ runDeliveryJobOperation cxt user gInfo = do
withWork_ a doWork (withStore' $ \db -> getNextDeliveryJob db deliveryKey) $ \job ->
processDeliveryJob job
`catchAllErrors` \e -> do
@@ -3772,7 +3775,7 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do
senders <- withStore' $ \db ->
fmap catMaybes . forM senderGMIds $ \sId ->
fmap eitherToMaybe . runExceptT $ do
- sender <- getNonRemovedMemberById db vr user sId
+ sender <- getNonRemovedMemberById db cxt user sId
vec <- getMemberRelationsVector db sender
pure (sender, vec)
let missingSenders = length senderGMIds - length senders
@@ -3788,7 +3791,7 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do
-- TODO [relays] public groups: revisit if mods/admins are introduced via this sidecar.
let (encoderErrs, validLabeled) =
partitionEithers
- [ (\bs -> (s, bs)) <$> encodeMemberNew vr gInfo s
+ [ (\bs -> (s, bs)) <$> encodeMemberNew (vr cxt) gInfo s
| (s, _) <- senders, memberRole' s <= GRMember
]
(extBody', inBody, overflowLabeled, large1) = batchProfilesWithBody maxEncodedMsgLength body validLabeled
@@ -3808,7 +3811,7 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do
where
sendLoop :: Int -> Maybe GroupMemberId -> Map GroupMemberId ByteString -> [(Int, (ByteString, [GroupMember]))] -> [GroupMember] -> ByteString -> [GroupMember] -> CM ()
sendLoop bucketSize cursorGMId_ senderVec overflowWithIds inBodySenders extBody activeSenders = do
- mems <- withStore' $ \db -> getGroupMembersByCursor db vr user gInfo cursorGMId_ singleSenderGMId_ bucketSize
+ mems <- withStore' $ \db -> getGroupMembersByCursor db cxt user gInfo cursorGMId_ singleSenderGMId_ bucketSize
unless (null mems) $ do
let msgReqs = buildMsgReqs mems
unless (null msgReqs) $ void $ withAgent (`sendMessages` msgReqs)
@@ -3853,7 +3856,7 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do
Nothing -> True
DJSMemberSupport scopeGMId -> do
-- for member support scope we just load all recipients in one go, without cursor
- modMs <- withStore' $ \db -> getGroupModerators db vr user gInfo
+ modMs <- withStore' $ \db -> getGroupModerators db cxt user gInfo
let moderatorFilter m =
memberCurrent m
&& maxVersion (memberChatVRange m) >= groupKnockingVersion
@@ -3863,14 +3866,14 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do
if Just scopeGMId == singleSenderGMId_
then pure modMs'
else do
- scopeMem <- withStore $ \db -> getGroupMemberById db vr user scopeGMId
+ scopeMem <- withStore $ \db -> getGroupMemberById db cxt user scopeGMId
pure $ scopeMem : modMs'
unless (null mems) $ deliver body mems
-- fully connected group
| otherwise = case singleSenderGMId_ of
Nothing -> throwChatError $ CEInternalError "delivery job worker: singleSenderGMId is required when not using relays"
Just sId -> do
- sender <- withStore $ \db -> getGroupMemberById db vr user sId
+ sender <- withStore $ \db -> getGroupMemberById db cxt user sId
ms <- buildMemberList sender
unless (null ms) $ deliver body ms
where
@@ -3880,14 +3883,14 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do
let introducedMemsIdxs = getRelationsIndexes MRIntroduced vec
case jobScope of
DJSGroup {jobSpec} -> do
- ms <- withStore' $ \db -> getGroupMembersByIndexes db vr user gInfo introducedMemsIdxs
+ ms <- withStore' $ \db -> getGroupMembersByIndexes db cxt user gInfo introducedMemsIdxs
pure $ filter shouldForwardTo ms
where
shouldForwardTo m
| jobSpecImpliedPending jobSpec = memberCurrentOrPending m
| otherwise = memberCurrent m
DJSMemberSupport scopeGMId -> do
- ms <- withStore' $ \db -> getSupportScopeMembersByIndexes db vr user gInfo scopeGMId introducedMemsIdxs
+ ms <- withStore' $ \db -> getSupportScopeMembersByIndexes db cxt user gInfo scopeGMId introducedMemsIdxs
pure $ filter shouldForwardTo ms
where
shouldForwardTo m = groupMemberId' m == scopeGMId || currentModerator m
@@ -3938,7 +3941,7 @@ getRelayRequestWorker hasWork = do
runRelayRequestWorker :: AgentClient -> Worker -> CM ()
runRelayRequestWorker a Worker {doWork} = do
- vr <- chatVersionRange
+ cxt <- chatStoreCxt
(user, uclId) <- withStore $ \db -> do
user <- getRelayUser db
UserContactLink {userContactLinkId} <- getUserAddress db user
@@ -3946,10 +3949,10 @@ runRelayRequestWorker a Worker {doWork} = do
delayThreads <- liftIO TM.emptyIO
forever $ do
lift $ waitForWork doWork
- runRelayRequestOperation delayThreads vr user uclId
+ runRelayRequestOperation delayThreads cxt user uclId
where
- runRelayRequestOperation :: TM.TMap GroupId (TMVar (Weak ThreadId)) -> VersionRangeChat -> User -> Int64 -> CM ()
- runRelayRequestOperation delayThreads vr user uclId =
+ runRelayRequestOperation :: TM.TMap GroupId (TMVar (Weak ThreadId)) -> StoreCxt -> User -> Int64 -> CM ()
+ runRelayRequestOperation delayThreads cxt user uclId =
withWork_ a doWork getReadyRelayRequest $
\(groupId, rrd) -> do
ChatConfig {relayRequestExpiry} <- asks config
@@ -3998,7 +4001,7 @@ runRelayRequestWorker a Worker {doWork} = do
processRelayRequest :: GroupId -> RelayRequestData -> CM ()
processRelayRequest groupId rrd = do
(gInfo, groupLink_) <- withStore $ \db -> do
- gInfo <- getGroupInfo db vr user groupId
+ gInfo <- getGroupInfo db cxt user groupId
groupLink_ <- liftIO $ runExceptT $ getGroupLink db user gInfo
pure (gInfo, groupLink_)
-- Check if relay link already exists (recovery case)
@@ -4026,7 +4029,7 @@ runRelayRequestWorker a Worker {doWork} = do
gInfo' <- withStore $ \db -> do
void $ updateGroupProfile db user gInfo gp
updateRelayGroupKeys db user gInfo pg rootKey memberPrivKey owners
- getGroupInfo db vr user groupId
+ getGroupInfo db cxt user groupId
pure (gInfo', sLnk)
where
validateGroupProfile :: GroupProfile -> CM ()
@@ -4058,5 +4061,5 @@ runRelayRequestWorker a Worker {doWork} = do
pure (sigKeys, sLnk)
acceptOwnerConnection :: RelayRequestData -> GroupInfo -> ShortLinkContact -> CM ()
acceptOwnerConnection RelayRequestData {relayInvId, reqChatVRange} gi relayLink = do
- ownerMember <- withStore $ \db -> getHostMember db vr user groupId
+ ownerMember <- withStore $ \db -> getHostMember db cxt user groupId
void $ acceptRelayJoinRequestAsync user uclId gi ownerMember relayInvId reqChatVRange relayLink
diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs
index fdb44b3707..dbe06ea283 100644
--- a/src/Simplex/Chat/Store/Connections.hs
+++ b/src/Simplex/Chat/Store/Connections.hs
@@ -74,8 +74,8 @@ getChatLockEntity db agentConnId = do
-- TODO consider whether ConnFailed connections should be excluded:
-- - from receiving: getConnectionEntity, getContactConnEntityByConnReqHash
-- - from subscribing: getContactConnsToSub, getUCLConnsToSub, getMemberConnsToSub, getPendingConnsToSub
-getConnectionEntity :: DB.Connection -> VersionRangeChat -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity
-getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
+getConnectionEntity :: DB.Connection -> StoreCxt -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity
+getConnectionEntity db cxt user@User {userId, userContactId} agentConnId = do
c@Connection {connType, entityId} <- getConnection_
case entityId of
Nothing ->
@@ -90,7 +90,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
where
getConnection_ :: ExceptT StoreError IO Connection
getConnection_ = ExceptT $ do
- firstRow (toConnection vr) (SEConnectionNotFound agentConnId) $
+ firstRow (toConnection cxt) (SEConnectionNotFound agentConnId) $
DB.query
db
[sql|
@@ -175,7 +175,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
liftIO $ bitraverse (addGroupChatTags db) pure gm
toGroupAndMember :: Connection -> GroupInfoRow :. GroupMemberRow -> (GroupInfo, GroupMember)
toGroupAndMember c (groupInfoRow :. memberRow) =
- let groupInfo = toGroupInfo vr userContactId [] groupInfoRow
+ let groupInfo = toGroupInfo cxt userContactId [] groupInfoRow
member = toGroupMember userContactId memberRow
in (groupInfo, (member :: GroupMember) {activeConn = Just c})
getUserContact_ :: Int64 -> ExceptT StoreError IO UserContact
@@ -194,17 +194,17 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
userContact_ [(cReq, groupId)] = Right UserContact {userContactLinkId, connReqContact = cReq, groupId}
userContact_ _ = Left SEUserContactLinkNotFound
-getConnectionEntityByConnReq :: DB.Connection -> VersionRangeChat -> User -> (ConnReqInvitation, ConnReqInvitation) -> IO (Maybe ConnectionEntity)
-getConnectionEntityByConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2) = do
+getConnectionEntityByConnReq :: DB.Connection -> StoreCxt -> User -> (ConnReqInvitation, ConnReqInvitation) -> IO (Maybe ConnectionEntity)
+getConnectionEntityByConnReq db cxt user@User {userId} (cReqSchema1, cReqSchema2) = do
connId_ <-
maybeFirstRow fromOnly $
DB.query db "SELECT agent_conn_id FROM connections WHERE user_id = ? AND conn_req_inv IN (?,?) LIMIT 1" (userId, cReqSchema1, cReqSchema2)
- maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db vr user) connId_
+ maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db cxt user) connId_
-getConnectionEntityViaShortLink :: DB.Connection -> VersionRangeChat -> User -> ShortLinkInvitation -> IO (Maybe (ConnReqInvitation, ConnectionEntity))
-getConnectionEntityViaShortLink db vr user@User {userId} shortLink = fmap eitherToMaybe $ runExceptT $ do
+getConnectionEntityViaShortLink :: DB.Connection -> StoreCxt -> User -> ShortLinkInvitation -> IO (Maybe (ConnReqInvitation, ConnectionEntity))
+getConnectionEntityViaShortLink db cxt user@User {userId} shortLink = fmap eitherToMaybe $ runExceptT $ do
(cReq, connId) <- ExceptT getConnReqConnId
- (cReq,) <$> getConnectionEntity db vr user connId
+ (cReq,) <$> getConnectionEntity db cxt user connId
where
getConnReqConnId =
firstRow' toConnReqConnId (SEInternalError "connection not found") $
@@ -225,8 +225,8 @@ getConnectionEntityViaShortLink db vr user@User {userId} shortLink = fmap either
-- multiple connections can have same via_contact_uri_hash if request was repeated;
-- this function searches for latest connection with contact so that "known contact" plan would be chosen;
-- deleted connections are filtered out to allow re-connecting via same contact address
-getContactConnEntityByConnReqHash :: DB.Connection -> VersionRangeChat -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe ConnectionEntity)
-getContactConnEntityByConnReqHash db vr user@User {userId} (cReqHash1, cReqHash2) = do
+getContactConnEntityByConnReqHash :: DB.Connection -> StoreCxt -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe ConnectionEntity)
+getContactConnEntityByConnReqHash db cxt user@User {userId} (cReqHash1, cReqHash2) = do
connId_ <-
maybeFirstRow fromOnly $
DB.query
@@ -243,7 +243,7 @@ getContactConnEntityByConnReqHash db vr user@User {userId} (cReqHash1, cReqHash2
) c
|]
(userId, cReqHash1, cReqHash2, ConnDeleted)
- maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db vr user) connId_
+ maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db cxt user) connId_
getContactConnsToSub :: DB.Connection -> User -> Bool -> IO [ConnId]
getContactConnsToSub db User {userId} filterToSubscribe =
diff --git a/src/Simplex/Chat/Store/ContactRequest.hs b/src/Simplex/Chat/Store/ContactRequest.hs
index bd2179cbd6..b4faef8491 100644
--- a/src/Simplex/Chat/Store/ContactRequest.hs
+++ b/src/Simplex/Chat/Store/ContactRequest.hs
@@ -49,7 +49,7 @@ import Database.SQLite.Simple.QQ (sql)
createOrUpdateContactRequest ::
DB.Connection ->
TVar ChaChaDRG ->
- VersionRangeChat ->
+ StoreCxt ->
User ->
Int64 ->
UserContactLink ->
@@ -65,7 +65,7 @@ createOrUpdateContactRequest ::
createOrUpdateContactRequest
db
gVar
- vr
+ cxt
user@User {userId, userContactId}
uclId
UserContactLink {addressSettings = AddressSettings {businessAddress}}
@@ -89,7 +89,7 @@ createOrUpdateContactRequest
Nothing ->
liftIO (getAcceptedBusinessChat xContactId) >>= \case
Just gInfo@GroupInfo {businessChat = Just BusinessChatInfo {customerId}} -> do
- clientMember <- getGroupMemberByMemberId db vr user gInfo customerId
+ clientMember <- getGroupMemberByMemberId db cxt user gInfo customerId
cr <- liftIO $ getContactRequestByXContactId xContactId
pure $ RSAcceptedRequest cr (REBusinessChat gInfo clientMember)
Just GroupInfo {businessChat = Nothing} -> throwError SEInvalidBusinessChatContactRequest
@@ -104,7 +104,7 @@ createOrUpdateContactRequest
getAcceptedContact :: XContactId -> IO (Maybe Contact)
getAcceptedContact xContactId = do
ct_ <-
- maybeFirstRow (toContact vr user []) $
+ maybeFirstRow (toContact cxt user []) $
DB.query
db
[sql|
@@ -128,7 +128,7 @@ createOrUpdateContactRequest
getAcceptedBusinessChat :: XContactId -> IO (Maybe GroupInfo)
getAcceptedBusinessChat xContactId = do
g_ <-
- maybeFirstRow (toGroupInfo vr userContactId []) $
+ maybeFirstRow (toGroupInfo cxt userContactId []) $
DB.query
db
(groupInfoQuery <> " WHERE g.business_xcontact_id = ? AND g.user_id = ? AND mu.contact_id = ?")
@@ -200,12 +200,12 @@ createOrUpdateContactRequest
"UPDATE contact_requests SET contact_id = ? WHERE contact_request_id = ?"
(contactId, contactRequestId)
ucr <- getContactRequest db user contactRequestId
- ct <- getContact db vr user contactId
+ ct <- getContact db cxt user contactId
pure $ RSCurrentRequest Nothing ucr (Just $ REContact ct)
createBusinessChat = do
let groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs $ preferences' user
(gInfo@GroupInfo {groupId}, clientMember) <-
- createBusinessRequestGroup db vr gVar user cReqChatVRange profile profileId ldn groupPreferences
+ createBusinessRequestGroup db cxt gVar user cReqChatVRange profile profileId ldn groupPreferences
liftIO $
DB.execute
db
@@ -278,13 +278,13 @@ createOrUpdateContactRequest
getRequestEntity UserContactRequest {contactRequestId, contactId_, businessGroupId_} =
case (contactId_, businessGroupId_) of
(Just contactId, Nothing) -> do
- ct <- getContact db vr user contactId
+ ct <- getContact db cxt user contactId
pure $ Just (REContact ct)
(Nothing, Just businessGroupId) -> do
- gInfo <- getGroupInfo db vr user businessGroupId
+ gInfo <- getGroupInfo db cxt user businessGroupId
case gInfo of
GroupInfo {businessChat = Just BusinessChatInfo {customerId}} -> do
- clientMember <- getGroupMemberByMemberId db vr user gInfo customerId
+ clientMember <- getGroupMemberByMemberId db cxt user gInfo customerId
pure $ Just (REBusinessChat gInfo clientMember)
_ -> throwError SEInvalidBusinessChatContactRequest
(Nothing, Nothing) -> pure Nothing
diff --git a/src/Simplex/Chat/Store/Delivery.hs b/src/Simplex/Chat/Store/Delivery.hs
index 75345e5e86..5e2e45f278 100644
--- a/src/Simplex/Chat/Store/Delivery.hs
+++ b/src/Simplex/Chat/Store/Delivery.hs
@@ -348,8 +348,8 @@ updateDeliveryJobStatus_ db jobId status errReason_ = do
(status, errReason_, currentTs, jobId)
-- TODO [relays] possible improvement is to prioritize owners and "active" members
-getGroupMembersByCursor :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe GroupMemberId -> Maybe GroupMemberId -> Int -> IO [GroupMember]
-getGroupMembersByCursor db vr user@User {userContactId} GroupInfo {groupId} cursorGMId_ singleSenderGMId_ count = do
+getGroupMembersByCursor :: DB.Connection -> StoreCxt -> User -> GroupInfo -> Maybe GroupMemberId -> Maybe GroupMemberId -> Int -> IO [GroupMember]
+getGroupMembersByCursor db cxt user@User {userContactId} GroupInfo {groupId} cursorGMId_ singleSenderGMId_ count = do
gmIds :: [Int64] <-
map fromOnly <$> case cursorGMId_ of
Nothing ->
@@ -367,13 +367,13 @@ getGroupMembersByCursor db vr user@User {userContactId} GroupInfo {groupId} curs
:. (cursorGMId, count)
)
#if defined(dbPostgres)
- map (toContactMember vr user) <$>
+ map (toContactMember cxt user) <$>
DB.query
db
- (groupMemberQuery <> " WHERE m.group_member_id IN ?")
+ (groupMemberQuery <> " WHERE m.group_member_id IN ? ORDER BY m.group_member_id ASC")
(Only (In gmIds))
#else
- rights <$> mapM (runExceptT . getGroupMemberById db vr user) gmIds
+ rights <$> mapM (runExceptT . getGroupMemberById db cxt user) gmIds
#endif
where
query =
diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs
index 71eb124bb9..886797f94a 100644
--- a/src/Simplex/Chat/Store/Direct.hs
+++ b/src/Simplex/Chat/Store/Direct.hs
@@ -249,8 +249,8 @@ createRelayMemberConnectionAsync db user@User {userId} gInfo GroupMember {groupM
where
customUserProfileId_ = localProfileId <$> incognitoMembershipProfile gInfo
-createRelayTestConnection :: DB.Connection -> VersionRangeChat -> User -> ConnId -> ConnStatus -> VersionChat -> SubscriptionMode -> ExceptT StoreError IO Connection
-createRelayTestConnection db vr user@User {userId} agentConnId connStatus chatV subMode = do
+createRelayTestConnection :: DB.Connection -> StoreCxt -> User -> ConnId -> ConnStatus -> VersionChat -> SubscriptionMode -> ExceptT StoreError IO Connection
+createRelayTestConnection db cxt user@User {userId} agentConnId connStatus chatV subMode = do
currentTs <- liftIO getCurrentTime
liftIO $
DB.execute
@@ -267,7 +267,7 @@ createRelayTestConnection db vr user@User {userId} agentConnId connStatus chatV
:. (BI True, currentTs, currentTs)
)
connId <- liftIO $ insertedRowId db
- getConnectionById db vr user connId
+ getConnectionById db cxt user connId
updateConnLinkData :: DB.Connection -> User -> Connection -> ConnReqContact -> ConnReqUriHash -> Maybe GroupLinkId -> VersionChat -> PQSupport -> IO ()
updateConnLinkData db User {userId} Connection {connId} cReq cReqHash groupLinkId_ chatV pqSup = do
@@ -291,13 +291,13 @@ setPreparedGroupStartedConnection db groupId = do
"UPDATE groups SET conn_link_started_connection = ?, updated_at = ? WHERE group_id = ?"
(BI True, currentTs, groupId)
-getConnReqContactXContactId :: DB.Connection -> VersionRangeChat -> User -> ConnReqUriHash -> ConnReqUriHash -> IO (Either (Maybe Connection) Contact)
-getConnReqContactXContactId db vr user@User {userId} cReqHash1 cReqHash2 =
- getContactByConnReqHash db vr user cReqHash1 cReqHash2 >>= maybe (Left <$> getConnection) (pure . Right)
+getConnReqContactXContactId :: DB.Connection -> StoreCxt -> User -> ConnReqUriHash -> ConnReqUriHash -> IO (Either (Maybe Connection) Contact)
+getConnReqContactXContactId db cxt user@User {userId} cReqHash1 cReqHash2 =
+ getContactByConnReqHash db cxt user cReqHash1 cReqHash2 >>= maybe (Left <$> getConnection) (pure . Right)
where
getConnection :: IO (Maybe Connection)
getConnection =
- maybeFirstRow (toConnection vr) $
+ maybeFirstRow (toConnection cxt) $
DB.query
db
[sql|
@@ -311,10 +311,10 @@ getConnReqContactXContactId db vr user@User {userId} cReqHash1 cReqHash2 =
|]
(userId, cReqHash1, userId, cReqHash2)
-getContactByConnReqHash :: DB.Connection -> VersionRangeChat -> User -> ConnReqUriHash -> ConnReqUriHash -> IO (Maybe Contact)
-getContactByConnReqHash db vr user@User {userId} cReqHash1 cReqHash2 = do
+getContactByConnReqHash :: DB.Connection -> StoreCxt -> User -> ConnReqUriHash -> ConnReqUriHash -> IO (Maybe Contact)
+getContactByConnReqHash db cxt user@User {userId} cReqHash1 cReqHash2 = do
ct <-
- maybeFirstRow (toContact vr user []) $
+ maybeFirstRow (toContact cxt user []) $
DB.query
db
[sql|
@@ -405,19 +405,19 @@ createIncognitoProfile db User {userId} p = do
-- contact_profiles row whose peer-claimed simplex_name was cleared to make
-- room for the new contact's claim, so the caller can emit
-- CEvtSimplexNameConflict.
-createPreparedContact :: DB.Connection -> VersionRangeChat -> User -> Profile -> ACreatedConnLink -> Maybe SharedMsgId -> Maybe SimplexNameInfo -> ExceptT StoreError IO (Contact, Maybe ContactName)
-createPreparedContact db vr user p connLinkToConnect welcomeSharedMsgId simplexName = do
+createPreparedContact :: DB.Connection -> StoreCxt -> User -> Profile -> ACreatedConnLink -> Maybe SharedMsgId -> Maybe SimplexNameInfo -> ExceptT StoreError IO (Contact, Maybe ContactName)
+createPreparedContact db cxt user p connLinkToConnect welcomeSharedMsgId simplexName = do
currentTs <- liftIO getCurrentTime
let prepared = Just (connLinkToConnect, welcomeSharedMsgId)
ctUserPreferences = newContactUserPrefs user p
(contactId, displaced) <- createContact_ db user p ctUserPreferences prepared "" currentTs simplexName
- ct <- getContact db vr user contactId
+ ct <- getContact db cxt user contactId
pure (ct, displaced)
-updatePreparedContactUser :: DB.Connection -> VersionRangeChat -> User -> Contact -> User -> ExceptT StoreError IO Contact
+updatePreparedContactUser :: DB.Connection -> StoreCxt -> User -> Contact -> User -> ExceptT StoreError IO Contact
updatePreparedContactUser
db
- vr
+ cxt
user
Contact {contactId, localDisplayName = oldLDN, profile = profile@LocalProfile {profileId, displayName}}
newUser@User {userId = newUserId} = do
@@ -450,16 +450,16 @@ updatePreparedContactUser
|]
(newUserId, currentTs, contactId)
safeDeleteLDN db user oldLDN
- getContact db vr newUser contactId
+ getContact db cxt newUser contactId
-- | Returns (contact, displaced) — see createPreparedContact for displaced.
-createDirectContact :: DB.Connection -> VersionRangeChat -> User -> Connection -> Profile -> Maybe SimplexNameInfo -> ExceptT StoreError IO (Contact, Maybe ContactName)
-createDirectContact db vr user Connection {connId, localAlias} p simplexName = do
+createDirectContact :: DB.Connection -> StoreCxt -> User -> Connection -> Profile -> Maybe SimplexNameInfo -> ExceptT StoreError IO (Contact, Maybe ContactName)
+createDirectContact db cxt user Connection {connId, localAlias} p simplexName = do
currentTs <- liftIO getCurrentTime
let ctUserPreferences = newContactUserPrefs user p
(contactId, displaced) <- createContact_ db user p ctUserPreferences Nothing localAlias currentTs simplexName
liftIO $ DB.execute db "UPDATE connections SET contact_id = ?, updated_at = ? WHERE connection_id = ?" (contactId, currentTs, connId)
- ct <- getContact db vr user contactId
+ ct <- getContact db cxt user contactId
pure (ct, displaced)
deleteContactConnections :: DB.Connection -> User -> Contact -> IO ()
@@ -514,13 +514,13 @@ deleteContactWithoutGroups db user@User {userId} ct@Contact {contactId, localDis
deleteUnusedIncognitoProfileById_ db user profileId
-- TODO remove in future versions: only used for legacy contact cleanup
-getDeletedContacts :: DB.Connection -> VersionRangeChat -> User -> IO [Contact]
-getDeletedContacts db vr user@User {userId} = do
+getDeletedContacts :: DB.Connection -> StoreCxt -> User -> IO [Contact]
+getDeletedContacts db cxt user@User {userId} = do
contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND deleted = 1" (Only userId)
- rights <$> mapM (runExceptT . getDeletedContact db vr user) contactIds
+ rights <$> mapM (runExceptT . getDeletedContact db cxt user) contactIds
-getDeletedContact :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO Contact
-getDeletedContact db vr user contactId = getContact_ db vr user contactId True
+getDeletedContact :: DB.Connection -> StoreCxt -> User -> Int64 -> ExceptT StoreError IO Contact
+getDeletedContact db cxt user contactId = getContact_ db cxt user contactId True
deleteContactProfile_ :: DB.Connection -> UserId -> ContactId -> IO ()
deleteContactProfile_ db userId contactId =
@@ -801,16 +801,16 @@ updateContactLDN_ db user@User {userId} contactId displayName newName updatedAt
(newName, updatedAt, userId, contactId)
safeDeleteLDN db user displayName
-getContactByName :: DB.Connection -> VersionRangeChat -> User -> ContactName -> ExceptT StoreError IO Contact
-getContactByName db vr user localDisplayName = do
+getContactByName :: DB.Connection -> StoreCxt -> User -> ContactName -> ExceptT StoreError IO Contact
+getContactByName db cxt user localDisplayName = do
cId <- getContactIdByName db user localDisplayName
- getContact db vr user cId
+ getContact db cxt user cId
-getContactBySimplexName :: DB.Connection -> VersionRangeChat -> User -> SimplexNameInfo -> ExceptT StoreError IO (Maybe Contact)
-getContactBySimplexName db vr user ni =
+getContactBySimplexName :: DB.Connection -> StoreCxt -> User -> SimplexNameInfo -> ExceptT StoreError IO (Maybe Contact)
+getContactBySimplexName db cxt user ni =
liftIO (getContactIdBySimplexName db user ni) >>= \case
Nothing -> pure Nothing
- Just cId -> Just <$> getContact db vr user cId
+ Just cId -> Just <$> getContact db cxt user cId
getContactIdBySimplexName :: DB.Connection -> User -> SimplexNameInfo -> IO (Maybe Int64)
getContactIdBySimplexName db User {userId} ni =
@@ -823,10 +823,10 @@ getContactIdBySimplexName db User {userId} ni =
|]
(userId, ni)
-getUserContacts :: DB.Connection -> VersionRangeChat -> User -> IO [Contact]
-getUserContacts db vr user@User {userId} = do
+getUserContacts :: DB.Connection -> StoreCxt -> User -> IO [Contact]
+getUserContacts db cxt user@User {userId} = do
contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND deleted = 0" (Only userId)
- contacts <- rights <$> mapM (runExceptT . getContact db vr user) contactIds
+ contacts <- rights <$> mapM (runExceptT . getContact db cxt user) contactIds
pure $ filter (\Contact {activeConn} -> isJust activeConn) contacts
getUserContactLinkIdByCReq :: DB.Connection -> Int64 -> ExceptT StoreError IO (Maybe Int64)
@@ -954,22 +954,22 @@ getContactIdByName db User {userId} cName =
ExceptT . firstRow fromOnly (SEContactNotFoundByName cName) $
DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND local_display_name = ? AND deleted = 0" (userId, cName)
-getContactViaShortLinkToConnect :: forall c. ConnectionModeI c => DB.Connection -> VersionRangeChat -> User -> ConnShortLink c -> ExceptT StoreError IO (Maybe (ConnectionRequestUri c, Contact))
-getContactViaShortLinkToConnect db vr user@User {userId} shortLink = do
+getContactViaShortLinkToConnect :: forall c. ConnectionModeI c => DB.Connection -> StoreCxt -> User -> ConnShortLink c -> ExceptT StoreError IO (Maybe (ConnectionRequestUri c, Contact))
+getContactViaShortLinkToConnect db cxt user@User {userId} shortLink = do
liftIO (maybeFirstRow id $ DB.query db "SELECT contact_id, conn_full_link_to_connect FROM contacts WHERE user_id = ? AND conn_short_link_to_connect = ?" (userId, shortLink)) >>= \case
Just (ctId :: Int64, Just (ACR cMode cReq)) ->
case testEquality cMode (sConnectionMode @c) of
- Just Refl -> Just . (cReq,) <$> getContact db vr user ctId
+ Just Refl -> Just . (cReq,) <$> getContact db cxt user ctId
Nothing -> pure Nothing
_ -> pure Nothing
-getContact :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO Contact
-getContact db vr user contactId = getContact_ db vr user contactId False
+getContact :: DB.Connection -> StoreCxt -> User -> Int64 -> ExceptT StoreError IO Contact
+getContact db cxt user contactId = getContact_ db cxt user contactId False
-getContact_ :: DB.Connection -> VersionRangeChat -> User -> Int64 -> Bool -> ExceptT StoreError IO Contact
-getContact_ db vr user@User {userId} contactId deleted = do
+getContact_ :: DB.Connection -> StoreCxt -> User -> Int64 -> Bool -> ExceptT StoreError IO Contact
+getContact_ db cxt user@User {userId} contactId deleted = do
chatTags <- liftIO $ getDirectChatTags db contactId
- ExceptT . firstRow (toContact vr user chatTags) (SEContactNotFound contactId) $
+ ExceptT . firstRow (toContact cxt user chatTags) (SEContactNotFound contactId) $
DB.query
db
[sql|
@@ -996,8 +996,8 @@ getUserByContactRequestId db contactRequestId =
ExceptT . firstRow toUser (SEUserNotFoundByContactRequestId contactRequestId) $
DB.query db (userQuery <> " JOIN contact_requests cr ON cr.user_id = u.user_id WHERE cr.contact_request_id = ?") (Only contactRequestId)
-getContactConnections :: DB.Connection -> VersionRangeChat -> UserId -> Contact -> IO [Connection]
-getContactConnections db vr userId Contact {contactId} =
+getContactConnections :: DB.Connection -> StoreCxt -> UserId -> Contact -> IO [Connection]
+getContactConnections db cxt userId Contact {contactId} =
connections =<< liftIO getConnections_
where
getConnections_ =
@@ -1014,11 +1014,11 @@ getContactConnections db vr userId Contact {contactId} =
|]
(userId, userId, contactId)
connections [] = pure []
- connections rows = pure $ map (toConnection vr) rows
+ connections rows = pure $ map (toConnection cxt) rows
-getConnectionById :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO Connection
-getConnectionById db vr User {userId} connId = ExceptT $ do
- firstRow (toConnection vr) (SEConnectionNotFoundById connId) $
+getConnectionById :: DB.Connection -> StoreCxt -> User -> Int64 -> ExceptT StoreError IO Connection
+getConnectionById db cxt User {userId} connId = ExceptT $ do
+ firstRow (toConnection cxt) (SEConnectionNotFoundById connId) $
DB.query
db
[sql|
diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs
index 951fce8958..5289a3b304 100644
--- a/src/Simplex/Chat/Store/Files.hs
+++ b/src/Simplex/Chat/Store/Files.hs
@@ -570,19 +570,19 @@ getRcvFileTransfer_ db userId fileId = do
Just fp -> pure fp
cancelled = maybe False unBI cancelled_
-acceptRcvInlineFT :: DB.Connection -> VersionRangeChat -> User -> FileTransferId -> FilePath -> ExceptT StoreError IO AChatItem
-acceptRcvInlineFT db vr user fileId filePath = do
+acceptRcvInlineFT :: DB.Connection -> StoreCxt -> User -> FileTransferId -> FilePath -> ExceptT StoreError IO AChatItem
+acceptRcvInlineFT db cxt user fileId filePath = do
liftIO $ acceptRcvFT_ db user fileId filePath False (Just IFMOffer) =<< getCurrentTime
- getChatItemByFileId db vr user fileId
+ getChatItemByFileId db cxt user fileId
startRcvInlineFT :: DB.Connection -> User -> RcvFileTransfer -> FilePath -> Maybe InlineFileMode -> IO ()
startRcvInlineFT db user RcvFileTransfer {fileId} filePath rcvFileInline =
acceptRcvFT_ db user fileId filePath False rcvFileInline =<< getCurrentTime
-xftpAcceptRcvFT :: DB.Connection -> VersionRangeChat -> User -> FileTransferId -> FilePath -> Bool -> ExceptT StoreError IO AChatItem
-xftpAcceptRcvFT db vr user fileId filePath userApprovedRelays = do
+xftpAcceptRcvFT :: DB.Connection -> StoreCxt -> User -> FileTransferId -> FilePath -> Bool -> ExceptT StoreError IO AChatItem
+xftpAcceptRcvFT db cxt user fileId filePath userApprovedRelays = do
liftIO $ acceptRcvFT_ db user fileId filePath userApprovedRelays Nothing =<< getCurrentTime
- getChatItemByFileId db vr user fileId
+ getChatItemByFileId db cxt user fileId
acceptRcvFT_ :: DB.Connection -> User -> FileTransferId -> FilePath -> Bool -> Maybe InlineFileMode -> UTCTime -> IO ()
acceptRcvFT_ db User {userId} fileId filePath userApprovedRelays rcvFileInline currentTs = do
@@ -860,9 +860,9 @@ getLocalCryptoFile db userId fileId sent =
pure $ CryptoFile filePath fileCryptoArgs
_ -> throwError $ SEFileNotFound fileId
-updateDirectCIFileStatus :: forall d. MsgDirectionI d => DB.Connection -> VersionRangeChat -> User -> Int64 -> CIFileStatus d -> ExceptT StoreError IO AChatItem
-updateDirectCIFileStatus db vr user fileId fileStatus = do
- aci@(AChatItem cType d cInfo ci) <- getChatItemByFileId db vr user fileId
+updateDirectCIFileStatus :: forall d. MsgDirectionI d => DB.Connection -> StoreCxt -> User -> Int64 -> CIFileStatus d -> ExceptT StoreError IO AChatItem
+updateDirectCIFileStatus db cxt user fileId fileStatus = do
+ aci@(AChatItem cType d cInfo ci) <- getChatItemByFileId db cxt user fileId
case (cType, testEquality d $ msgDirection @d) of
(SCTDirect, Just Refl) -> do
liftIO $ updateCIFileStatus db user fileId fileStatus
diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs
index 7004d61066..ce5a36e93c 100644
--- a/src/Simplex/Chat/Store/Groups.hs
+++ b/src/Simplex/Chat/Store/Groups.hs
@@ -257,9 +257,9 @@ createGroupLink db gVar user@User {userId} groupInfo@GroupInfo {groupId, localDi
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode PQSupportOff Nothing
getGroupLink db user groupInfo
-getGroupLinkConnection :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> ExceptT StoreError IO Connection
-getGroupLinkConnection db vr User {userId} groupInfo@GroupInfo {groupId} =
- ExceptT . firstRow (toConnection vr) (SEGroupLinkNotFound groupInfo) $
+getGroupLinkConnection :: DB.Connection -> StoreCxt -> User -> GroupInfo -> ExceptT StoreError IO Connection
+getGroupLinkConnection db cxt User {userId} groupInfo@GroupInfo {groupId} =
+ ExceptT . firstRow (toConnection cxt) (SEGroupLinkNotFound groupInfo) $
DB.query
db
[sql|
@@ -354,8 +354,8 @@ setGroupLinkShortLink db gLnk@GroupLink {userContactLinkId, connLinkContact = CC
pure gLnk {connLinkContact = CCLink connFullLink (Just shortLink), shortLinkDataSet = True, shortLinkLargeDataSet = BoolDef True}
-- | creates completely new group with a single member - the current user
-createNewGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> Maybe Profile -> Bool -> MemberId -> Maybe GroupKeys -> Maybe Int64 -> ExceptT StoreError IO GroupInfo
-createNewGroup db vr user@User {userId} groupProfile incognitoProfile useRelays memberId groupKeys publicMemberCount_ = ExceptT $ do
+createNewGroup :: DB.Connection -> StoreCxt -> User -> GroupProfile -> Maybe Profile -> Bool -> MemberId -> Maybe GroupKeys -> Maybe Int64 -> ExceptT StoreError IO GroupInfo
+createNewGroup db cxt user@User {userId} groupProfile incognitoProfile useRelays memberId groupKeys publicMemberCount_ = ExceptT $ do
let GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup, groupPreferences, memberAdmission} = groupProfile
(groupType_, groupLink_, publicGroupId_) = case publicGroup of
Just PublicGroupProfile {groupType, groupLink, publicGroupId} -> (Just groupType, Just groupLink, Just publicGroupId)
@@ -399,7 +399,7 @@ createNewGroup db vr user@User {userId} groupProfile incognitoProfile useRelays
)
insertedRowId db
let memberPubKey = C.publicKey . memberPrivKey <$> groupKeys
- membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole memberId GROwner) GCUserMember GSMemCreator IBUser customUserProfileId memberPubKey currentTs vr
+ membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole memberId GROwner) GCUserMember GSMemCreator IBUser customUserProfileId memberPubKey currentTs (vr cxt)
let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False}
pure
GroupInfo
@@ -431,13 +431,13 @@ createNewGroup db vr user@User {userId} groupProfile incognitoProfile useRelays
}
-- | creates a new group record for the group the current user was invited to, or returns an existing one
-createGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId)
+createGroupInvitation :: DB.Connection -> StoreCxt -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId)
createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName
-createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, business} incognitoProfileId = do
+createGroupInvitation db cxt user@User {userId} contact@Contact {contactId, activeConn = Just Connection {peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, business} incognitoProfileId = do
liftIO getInvitationGroupId_ >>= \case
Nothing -> createGroupInvitation_
Just gId -> do
- gInfo@GroupInfo {membership, groupProfile = p'} <- getGroupInfo db vr user gId
+ gInfo@GroupInfo {membership, groupProfile = p'} <- getGroupInfo db cxt user gId
hostId <- getHostMemberId_ db user gId
let GroupMember {groupMemberId, memberId, memberRole} = membership
MemberIdRole {memberId = invMemberId, memberRole = invMemberRole} = invitedMember
@@ -476,9 +476,9 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
|]
((profileId, localDisplayName, connRequest, userId, BI True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
insertedRowId db
- let hostVRange = adjustedMemberVRange vr peerChatVRange
+ let hostVRange = adjustedMemberVRange (vr cxt) peerChatVRange
GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing Nothing currentTs hostVRange
- membership <- createContactMemberInv_ db user groupId (Just groupMemberId) user invitedMember GCUserMember GSMemInvited (IBContact contactId) incognitoProfileId Nothing currentTs vr
+ membership <- createContactMemberInv_ db user groupId (Just groupMemberId) user invitedMember GCUserMember GSMemInvited (IBContact contactId) incognitoProfileId Nothing currentTs (vr cxt)
let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False}
pure
( GroupInfo
@@ -622,8 +622,8 @@ deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile {
DB.execute db "DELETE FROM contacts WHERE contact_id = ?" (Only contactId)
DB.execute db "DELETE FROM contact_profiles WHERE contact_profile_id = ?" (Only profileId)
-createPreparedGroup :: DB.Connection -> TVar ChaChaDRG -> VersionRangeChat -> User -> GroupProfile -> Bool -> CreatedLinkContact -> Maybe SharedMsgId -> Bool -> GroupMemberRole -> Maybe Int64 -> Maybe SimplexNameInfo -> ExceptT StoreError IO (GroupInfo, Maybe GroupMember)
-createPreparedGroup db gVar vr user@User {userId, userContactId} groupProfile business connLinkToConnect welcomeSharedMsgId useRelays userMemberRole publicMemberCount_ simplexName = do
+createPreparedGroup :: DB.Connection -> TVar ChaChaDRG -> StoreCxt -> User -> GroupProfile -> Bool -> CreatedLinkContact -> Maybe SharedMsgId -> Bool -> GroupMemberRole -> Maybe Int64 -> Maybe SimplexNameInfo -> ExceptT StoreError IO (GroupInfo, Maybe GroupMember)
+createPreparedGroup db gVar cxt user@User {userId, userContactId} groupProfile business connLinkToConnect welcomeSharedMsgId useRelays userMemberRole publicMemberCount_ simplexName = do
currentTs <- liftIO getCurrentTime
let prepared = Just (connLinkToConnect, welcomeSharedMsgId)
(groupId, groupLDN) <- createGroup_ db userId groupProfile prepared Nothing useRelays Nothing publicMemberCount_ currentTs simplexName
@@ -637,11 +637,11 @@ createPreparedGroup db gVar vr user@User {userId, userContactId} groupProfile bu
else pure $ MemberId $ encodeUtf8 groupLDN <> "_user_unknown_id"
let userMember = MemberIdRole userMemberId userMemberRole
-- TODO [member keys] user key must be included here. Should key be added when group is prepared?
- membership <- createContactMemberInv_ db user groupId hostMemberId_ user userMember GCUserMember GSMemUnknown IBUnknown Nothing Nothing currentTs vr
- hostMember_ <- forM hostMemberId_ $ getGroupMember db vr user groupId
+ membership <- createContactMemberInv_ db user groupId hostMemberId_ user userMember GCUserMember GSMemUnknown IBUnknown Nothing Nothing currentTs (vr cxt)
+ hostMember_ <- forM hostMemberId_ $ getGroupMember db cxt user groupId
forM_ hostMember_ $ \hostMember ->
when business $ liftIO $ setGroupBusinessChatInfo groupId membership hostMember
- g <- getGroupInfo db vr user groupId
+ g <- getGroupInfo db cxt user groupId
pure (g, hostMember_)
where
insertHost_ currentTs groupId groupLDN = do
@@ -681,13 +681,13 @@ updateBusinessChatInfo db groupId businessChatInfo =
|]
(businessChatInfoRow businessChatInfo :. (Only groupId))
-updatePreparedGroupUser :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe GroupMember -> User -> ExceptT StoreError IO GroupInfo
-updatePreparedGroupUser db vr user gInfo@GroupInfo {groupId, membership} hostMember_ newUser@User {userId = newUserId} = do
+updatePreparedGroupUser :: DB.Connection -> StoreCxt -> User -> GroupInfo -> Maybe GroupMember -> User -> ExceptT StoreError IO GroupInfo
+updatePreparedGroupUser db cxt user gInfo@GroupInfo {groupId, membership} hostMember_ newUser@User {userId = newUserId} = do
currentTs <- liftIO getCurrentTime
updateGroup gInfo currentTs
liftIO $ updateMembership membership currentTs
forM_ hostMember_ $ \hostMember -> updateHostMember hostMember currentTs
- getGroupInfo db vr newUser groupId
+ getGroupInfo db cxt newUser groupId
where
updateGroup GroupInfo {localDisplayName = oldGroupLDN, groupProfile = GroupProfile {displayName = groupDisplayName}} currentTs =
ExceptT . withLocalDisplayName db newUserId groupDisplayName $ \newGroupLDN -> runExceptT $ do
@@ -753,21 +753,21 @@ updatePreparedGroupUser db vr user gInfo@GroupInfo {groupId, membership} hostMem
(newUserId, currentTs, hostProfileId)
safeDeleteLDN db user oldHostLDN
-updatePreparedUserAndHostMembersInvited :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember)
-updatePreparedUserAndHostMembersInvited db vr user gInfo hostMember GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, accepted, business} = do
+updatePreparedUserAndHostMembersInvited :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupMember -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember)
+updatePreparedUserAndHostMembersInvited db cxt user gInfo hostMember GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, accepted, business} = do
let fromMemberProfile = profileFromName fromMemberName
initialStatus = maybe GSMemAccepted (acceptanceToStatus $ memberAdmission groupProfile) accepted
- updatePreparedUserAndHostMembers' db vr user gInfo hostMember fromMember fromMemberProfile invitedMember groupProfile business initialStatus
+ updatePreparedUserAndHostMembers' db cxt user gInfo hostMember fromMember fromMemberProfile invitedMember groupProfile business initialStatus
-updatePreparedUserAndHostMembersRejected :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> GroupLinkRejection -> ExceptT StoreError IO (GroupInfo, GroupMember)
-updatePreparedUserAndHostMembersRejected db vr user gInfo hostMember GroupLinkRejection {fromMember = fromMember@MemberIdRole {memberId}, invitedMember, groupProfile} = do
+updatePreparedUserAndHostMembersRejected :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupMember -> GroupLinkRejection -> ExceptT StoreError IO (GroupInfo, GroupMember)
+updatePreparedUserAndHostMembersRejected db cxt user gInfo hostMember GroupLinkRejection {fromMember = fromMember@MemberIdRole {memberId}, invitedMember, groupProfile} = do
let fromMemberProfile = profileFromName $ nameFromMemberId memberId
- updatePreparedUserAndHostMembers' db vr user gInfo hostMember fromMember fromMemberProfile invitedMember groupProfile Nothing GSMemRejected
+ updatePreparedUserAndHostMembers' db cxt user gInfo hostMember fromMember fromMemberProfile invitedMember groupProfile Nothing GSMemRejected
-updatePreparedUserAndHostMembers' :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> MemberIdRole -> Profile -> MemberIdRole -> GroupProfile -> Maybe BusinessChatInfo -> GroupMemberStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
+updatePreparedUserAndHostMembers' :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupMember -> MemberIdRole -> Profile -> MemberIdRole -> GroupProfile -> Maybe BusinessChatInfo -> GroupMemberStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
updatePreparedUserAndHostMembers'
db
- vr
+ cxt
user
gInfo@GroupInfo {groupId, membership, groupProfile = gp, businessChat}
hostMember
@@ -786,7 +786,7 @@ updatePreparedUserAndHostMembers'
void $ updateGroupProfile db user gInfo groupProfile
when (isJust businessChat && isJust business) $
liftIO $ updateBusinessChatInfo db groupId business
- gInfo' <- getGroupInfo db vr user groupId
+ gInfo' <- getGroupInfo db cxt user groupId
pure (gInfo', hostMember')
where
updateUserMember currentTs = do
@@ -817,23 +817,23 @@ updatePreparedUserAndHostMembers'
WHERE group_member_id = ?
|]
(memberId, memberRole, currentTs, gmId)
- getGroupMemberById db vr user gmId
+ getGroupMemberById db cxt user gmId
-createGroupInvitedViaLink :: DB.Connection -> VersionRangeChat -> User -> Connection -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember)
-createGroupInvitedViaLink db vr user conn GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, accepted, business} = do
+createGroupInvitedViaLink :: DB.Connection -> StoreCxt -> User -> Connection -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember)
+createGroupInvitedViaLink db cxt user conn GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, accepted, business} = do
let fromMemberProfile = profileFromName fromMemberName
initialStatus = maybe GSMemAccepted (acceptanceToStatus $ memberAdmission groupProfile) accepted
- createGroupViaLink' db vr user conn fromMember fromMemberProfile invitedMember groupProfile business initialStatus
+ createGroupViaLink' db cxt user conn fromMember fromMemberProfile invitedMember groupProfile business initialStatus
-createGroupRejectedViaLink :: DB.Connection -> VersionRangeChat -> User -> Connection -> GroupLinkRejection -> ExceptT StoreError IO (GroupInfo, GroupMember)
-createGroupRejectedViaLink db vr user conn GroupLinkRejection {fromMember = fromMember@MemberIdRole {memberId}, invitedMember, groupProfile} = do
+createGroupRejectedViaLink :: DB.Connection -> StoreCxt -> User -> Connection -> GroupLinkRejection -> ExceptT StoreError IO (GroupInfo, GroupMember)
+createGroupRejectedViaLink db cxt user conn GroupLinkRejection {fromMember = fromMember@MemberIdRole {memberId}, invitedMember, groupProfile} = do
let fromMemberProfile = profileFromName $ nameFromMemberId memberId
- createGroupViaLink' db vr user conn fromMember fromMemberProfile invitedMember groupProfile Nothing GSMemRejected
+ createGroupViaLink' db cxt user conn fromMember fromMemberProfile invitedMember groupProfile Nothing GSMemRejected
-createGroupViaLink' :: DB.Connection -> VersionRangeChat -> User -> Connection -> MemberIdRole -> Profile -> MemberIdRole -> GroupProfile -> Maybe BusinessChatInfo -> GroupMemberStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
+createGroupViaLink' :: DB.Connection -> StoreCxt -> User -> Connection -> MemberIdRole -> Profile -> MemberIdRole -> GroupProfile -> Maybe BusinessChatInfo -> GroupMemberStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
createGroupViaLink'
db
- vr
+ cxt
user@User {userId, userContactId}
Connection {connId, customUserProfileId}
fromMember
@@ -848,9 +848,9 @@ createGroupViaLink'
liftIO $ DB.execute db "UPDATE connections SET conn_type = ?, group_member_id = ?, updated_at = ? WHERE connection_id = ?" (ConnMember, hostMemberId, currentTs, connId)
-- using IBUnknown since host is created without contact
-- TODO [member keys] this is currently not used with public groups. If it needs to be used, member keys need to be added
- void $ createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember membershipStatus IBUnknown customUserProfileId Nothing currentTs vr
+ void $ createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember membershipStatus IBUnknown customUserProfileId Nothing currentTs (vr cxt)
liftIO $ setViaGroupLinkUri db groupId connId
- (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user hostMemberId
+ (,) <$> getGroupInfo db cxt user groupId <*> getGroupMemberById db cxt user hostMemberId
where
insertHost_ currentTs groupId = do
(localDisplayName, profileId) <- createNewMemberProfile_ db user fromMemberProfile currentTs
@@ -911,10 +911,10 @@ setGroupInvitationChatItemId db User {userId} groupId chatItemId = do
-- TODO return the last connection that is ready, not any last connection
-- requires updating connection status
-getGroup :: DB.Connection -> VersionRangeChat -> User -> GroupId -> ExceptT StoreError IO Group
-getGroup db vr user groupId = do
- gInfo <- getGroupInfo db vr user groupId
- members <- liftIO $ getGroupMembers db vr user gInfo
+getGroup :: DB.Connection -> StoreCxt -> User -> GroupId -> ExceptT StoreError IO Group
+getGroup db cxt user groupId = do
+ gInfo <- getGroupInfo db cxt user groupId
+ members <- liftIO $ getGroupMembers db cxt user gInfo
pure $ Group gInfo members
deleteGroupChatItems :: DB.Connection -> User -> GroupInfo -> IO ()
@@ -1008,18 +1008,18 @@ deleteGroupProfile_ db userId groupId =
|]
(userId, groupId)
-getInProgressGroups :: DB.Connection -> VersionRangeChat -> User -> UTCTime -> IO [GroupInfo]
-getInProgressGroups db vr user@User {userId} createdAtCutoff = do
+getInProgressGroups :: DB.Connection -> StoreCxt -> User -> UTCTime -> IO [GroupInfo]
+getInProgressGroups db cxt user@User {userId} createdAtCutoff = do
groupIds <- map fromOnly <$>
DB.query
db
"SELECT group_id FROM groups WHERE user_id = ? AND creating_in_progress = 1 AND created_at <= ?"
(userId, createdAtCutoff)
- rights <$> mapM (runExceptT . getGroupInfo db vr user) groupIds
+ rights <$> mapM (runExceptT . getGroupInfo db cxt user) groupIds
-getBaseGroupDetails :: DB.Connection -> VersionRangeChat -> User -> Maybe ContactId -> Maybe Text -> IO [GroupInfo]
-getBaseGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do
- map (toGroupInfo vr userContactId [])
+getBaseGroupDetails :: DB.Connection -> StoreCxt -> User -> Maybe ContactId -> Maybe Text -> IO [GroupInfo]
+getBaseGroupDetails db cxt User {userId, userContactId} _contactId_ search_ = do
+ map (toGroupInfo cxt userContactId [])
<$> DB.query db (groupInfoQuery <> " " <> condition) (userId, userContactId, search, search, search, search)
where
condition =
@@ -1047,16 +1047,16 @@ getContactGroupPreferences db User {userId} Contact {contactId} = do
|]
(userId, contactId)
-getGroupInfoByName :: DB.Connection -> VersionRangeChat -> User -> GroupName -> ExceptT StoreError IO GroupInfo
-getGroupInfoByName db vr user gName = do
+getGroupInfoByName :: DB.Connection -> StoreCxt -> User -> GroupName -> ExceptT StoreError IO GroupInfo
+getGroupInfoByName db cxt user gName = do
gId <- getGroupIdByName db user gName
- getGroupInfo db vr user gId
+ getGroupInfo db cxt user gId
-getGroupInfoBySimplexName :: DB.Connection -> VersionRangeChat -> User -> SimplexNameInfo -> ExceptT StoreError IO (Maybe GroupInfo)
-getGroupInfoBySimplexName db vr user ni =
+getGroupInfoBySimplexName :: DB.Connection -> StoreCxt -> User -> SimplexNameInfo -> ExceptT StoreError IO (Maybe GroupInfo)
+getGroupInfoBySimplexName db cxt user ni =
liftIO (getGroupIdBySimplexName db user ni) >>= \case
Nothing -> pure Nothing
- Just gId -> Just <$> getGroupInfo db vr user gId
+ Just gId -> Just <$> getGroupInfo db cxt user gId
-- | Unlike the parallel 'getContactBySimplexName' lookup (which filters
-- @ct.deleted = 0@ to match the @idx_contacts_simplex_name@ partial index),
@@ -1078,17 +1078,17 @@ getGroupIdBySimplexName db User {userId} ni =
maybeFirstRow fromOnly $
DB.query db "SELECT group_id FROM groups WHERE user_id = ? AND simplex_name = ?" (userId, ni)
-getGroupMember :: DB.Connection -> VersionRangeChat -> User -> GroupId -> GroupMemberId -> ExceptT StoreError IO GroupMember
-getGroupMember db vr user@User {userId} groupId groupMemberId =
- ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFound groupMemberId) $
+getGroupMember :: DB.Connection -> StoreCxt -> User -> GroupId -> GroupMemberId -> ExceptT StoreError IO GroupMember
+getGroupMember db cxt user@User {userId} groupId groupMemberId =
+ ExceptT . firstRow (toContactMember cxt user) (SEGroupMemberNotFound groupMemberId) $
DB.query
db
(groupMemberQuery <> " WHERE m.group_id = ? AND m.group_member_id = ? AND m.user_id = ?")
(groupId, groupMemberId, userId)
-getHostMember :: DB.Connection -> VersionRangeChat -> User -> GroupId -> ExceptT StoreError IO GroupMember
-getHostMember db vr user groupId =
- ExceptT . firstRow (toContactMember vr user) (SEGroupHostMemberNotFound groupId) $
+getHostMember :: DB.Connection -> StoreCxt -> User -> GroupId -> ExceptT StoreError IO GroupMember
+getHostMember db cxt user groupId =
+ ExceptT . firstRow (toContactMember cxt user) (SEGroupHostMemberNotFound groupId) $
DB.query
db
(groupMemberQuery <> " WHERE m.group_id = ? AND m.member_category = ?")
@@ -1127,54 +1127,54 @@ toMentionedMember (groupMemberId, memberId, memberRole, displayName, localAlias)
let memberRef = Just CIMentionMember {groupMemberId, displayName, localAlias, memberRole}
in CIMention {memberId, memberRef}
-getGroupMemberById :: DB.Connection -> VersionRangeChat -> User -> GroupMemberId -> ExceptT StoreError IO GroupMember
-getGroupMemberById db vr user@User {userId} groupMemberId =
- ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFound groupMemberId) $
+getGroupMemberById :: DB.Connection -> StoreCxt -> User -> GroupMemberId -> ExceptT StoreError IO GroupMember
+getGroupMemberById db cxt user@User {userId} groupMemberId =
+ ExceptT . firstRow (toContactMember cxt user) (SEGroupMemberNotFound groupMemberId) $
DB.query
db
(groupMemberQuery <> " WHERE m.group_member_id = ? AND m.user_id = ?")
(groupMemberId, userId)
-getNonRemovedMemberById :: DB.Connection -> VersionRangeChat -> User -> GroupMemberId -> ExceptT StoreError IO GroupMember
-getNonRemovedMemberById db vr user@User {userId} groupMemberId =
- ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFound groupMemberId) $
+getNonRemovedMemberById :: DB.Connection -> StoreCxt -> User -> GroupMemberId -> ExceptT StoreError IO GroupMember
+getNonRemovedMemberById db cxt user@User {userId} groupMemberId =
+ ExceptT . firstRow (toContactMember cxt user) (SEGroupMemberNotFound groupMemberId) $
DB.query
db
(groupMemberQuery <> " WHERE m.group_member_id = ? AND m.user_id = ? AND m.member_status NOT IN (?,?,?,?)")
(groupMemberId, userId, GSMemRejected, GSMemRemoved, GSMemLeft, GSMemGroupDeleted)
-getGroupMemberByIndex :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Int64 -> ExceptT StoreError IO GroupMember
-getGroupMemberByIndex db vr user GroupInfo {groupId} indexInGroup =
- ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFoundByIndex indexInGroup) $
+getGroupMemberByIndex :: DB.Connection -> StoreCxt -> User -> GroupInfo -> Int64 -> ExceptT StoreError IO GroupMember
+getGroupMemberByIndex db cxt user GroupInfo {groupId} indexInGroup =
+ ExceptT . firstRow (toContactMember cxt user) (SEGroupMemberNotFoundByIndex indexInGroup) $
DB.query
db
(groupMemberQuery <> " WHERE m.group_id = ? AND m.index_in_group = ?")
(groupId, indexInGroup)
-getSupportScopeMemberByIndex :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMemberId -> Int64 -> ExceptT StoreError IO GroupMember
-getSupportScopeMemberByIndex db vr user GroupInfo {groupId} scopeGMId indexInGroup =
- ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFoundByIndex indexInGroup) $
+getSupportScopeMemberByIndex :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupMemberId -> Int64 -> ExceptT StoreError IO GroupMember
+getSupportScopeMemberByIndex db cxt user GroupInfo {groupId} scopeGMId indexInGroup =
+ ExceptT . firstRow (toContactMember cxt user) (SEGroupMemberNotFoundByIndex indexInGroup) $
DB.query
db
(groupMemberQuery <> " WHERE m.group_id = ? AND m.index_in_group = ? AND (m.member_role IN (?,?,?) OR m.group_member_id = ?)")
(groupId, indexInGroup, GRModerator, GRAdmin, GROwner, scopeGMId)
-getGroupMemberByMemberId :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> ExceptT StoreError IO GroupMember
-getGroupMemberByMemberId db vr user GroupInfo {groupId} memberId =
- ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFoundByMemberId memberId) $
+getGroupMemberByMemberId :: DB.Connection -> StoreCxt -> User -> GroupInfo -> MemberId -> ExceptT StoreError IO GroupMember
+getGroupMemberByMemberId db cxt user GroupInfo {groupId} memberId =
+ ExceptT . firstRow (toContactMember cxt user) (SEGroupMemberNotFoundByMemberId memberId) $
DB.query
db
(groupMemberQuery <> " WHERE m.group_id = ? AND m.member_id = ?")
(groupId, memberId)
-getCreateUnknownGMByMemberId :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> ContactName -> GroupMemberRole -> Bool -> ExceptT StoreError IO (Maybe (GroupMember, Bool))
-getCreateUnknownGMByMemberId db vr user gInfo memberId memberName unknownMemberRole allowCreate = do
- liftIO (runExceptT $ getGroupMemberByMemberId db vr user gInfo memberId) >>= \case
+getCreateUnknownGMByMemberId :: DB.Connection -> StoreCxt -> User -> GroupInfo -> MemberId -> ContactName -> GroupMemberRole -> Bool -> ExceptT StoreError IO (Maybe (GroupMember, Bool))
+getCreateUnknownGMByMemberId db cxt user gInfo memberId memberName unknownMemberRole allowCreate = do
+ liftIO (runExceptT $ getGroupMemberByMemberId db cxt user gInfo memberId) >>= \case
Right m -> pure $ Just (m, False)
Left (SEGroupMemberNotFoundByMemberId _)
| allowCreate -> do
let name = if T.null memberName then nameFromMemberId memberId else memberName
- m <- createNewUnknownGroupMember db vr user gInfo memberId name unknownMemberRole
+ m <- createNewUnknownGroupMember db cxt user gInfo memberId name unknownMemberRole
pure $ Just (m, True)
| otherwise -> pure Nothing
Left e -> throwError e
@@ -1193,59 +1193,59 @@ getGroupMemberIdViaMemberId db User {userId} GroupInfo {groupId} memberId =
"SELECT group_member_id FROM group_members WHERE user_id = ? AND group_id = ? AND member_id = ?"
(userId, groupId, memberId)
-getGroupMembers :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember]
-getGroupMembers db vr user@User {userId, userContactId} GroupInfo {groupId} =
- map (toContactMember vr user)
+getGroupMembers :: DB.Connection -> StoreCxt -> User -> GroupInfo -> IO [GroupMember]
+getGroupMembers db cxt user@User {userId, userContactId} GroupInfo {groupId} =
+ map (toContactMember cxt user)
<$> DB.query
db
(groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?)")
(userId, groupId, userContactId)
-getGroupMembersByIndexes :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> [Int64] -> IO [GroupMember]
-getGroupMembersByIndexes db vr user gInfo indexesInGroup = do
+getGroupMembersByIndexes :: DB.Connection -> StoreCxt -> User -> GroupInfo -> [Int64] -> IO [GroupMember]
+getGroupMembersByIndexes db cxt user gInfo indexesInGroup = do
#if defined(dbPostgres)
let GroupInfo {groupId} = gInfo
- map (toContactMember vr user) <$>
+ map (toContactMember cxt user) <$>
DB.query
db
(groupMemberQuery <> " WHERE m.group_id = ? AND m.index_in_group IN ?")
(groupId, In indexesInGroup)
#else
- rights <$> mapM (runExceptT . getGroupMemberByIndex db vr user gInfo) indexesInGroup
+ rights <$> mapM (runExceptT . getGroupMemberByIndex db cxt user gInfo) indexesInGroup
#endif
-getSupportScopeMembersByIndexes :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMemberId -> [Int64] -> IO [GroupMember]
-getSupportScopeMembersByIndexes db vr user gInfo scopeGMId indexesInGroup = do
+getSupportScopeMembersByIndexes :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupMemberId -> [Int64] -> IO [GroupMember]
+getSupportScopeMembersByIndexes db cxt user gInfo scopeGMId indexesInGroup = do
#if defined(dbPostgres)
let GroupInfo {groupId} = gInfo
- map (toContactMember vr user) <$>
+ map (toContactMember cxt user) <$>
DB.query
db
(groupMemberQuery <> " WHERE m.group_id = ? AND m.index_in_group IN ? AND (m.member_role IN (?,?,?) OR m.group_member_id = ?)")
(groupId, In indexesInGroup, GRModerator, GRAdmin, GROwner, scopeGMId)
#else
- rights <$> mapM (runExceptT . getSupportScopeMemberByIndex db vr user gInfo scopeGMId) indexesInGroup
+ rights <$> mapM (runExceptT . getSupportScopeMemberByIndex db cxt user gInfo scopeGMId) indexesInGroup
#endif
-getGroupModerators :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember]
-getGroupModerators db vr user@User {userId, userContactId} GroupInfo {groupId} = do
- map (toContactMember vr user)
+getGroupModerators :: DB.Connection -> StoreCxt -> User -> GroupInfo -> IO [GroupMember]
+getGroupModerators db cxt user@User {userId, userContactId} GroupInfo {groupId} = do
+ map (toContactMember cxt user)
<$> DB.query
db
(groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?) AND m.member_role IN (?,?,?)")
(userId, groupId, userContactId, GRModerator, GRAdmin, GROwner)
-getGroupRelayMembers :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember]
-getGroupRelayMembers db vr user@User {userId, userContactId} GroupInfo {groupId} = do
- map (toContactMember vr user)
+getGroupRelayMembers :: DB.Connection -> StoreCxt -> User -> GroupInfo -> IO [GroupMember]
+getGroupRelayMembers db cxt user@User {userId, userContactId} GroupInfo {groupId} = do
+ map (toContactMember cxt user)
<$> DB.query
db
(groupMemberQuery <> " WHERE m.user_id = ? AND m.group_id = ? AND m.contact_id IS DISTINCT FROM ? AND m.member_role = ?")
(userId, groupId, userContactId, GRRelay)
-getGroupMembersForExpiration :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> IO [GroupMember]
-getGroupMembersForExpiration db vr user@User {userId, userContactId} GroupInfo {groupId} = do
- map (toContactMember vr user)
+getGroupMembersForExpiration :: DB.Connection -> StoreCxt -> User -> GroupInfo -> IO [GroupMember]
+getGroupMembersForExpiration db cxt user@User {userId, userContactId} GroupInfo {groupId} = do
+ map (toContactMember cxt user)
<$> DB.query
db
( groupMemberQuery
@@ -1260,22 +1260,22 @@ getGroupMembersForExpiration db vr user@User {userId, userContactId} GroupInfo {
)
(groupId, userId, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted, GSMemUnknown)
-getRemovedMembersToCleanup :: DB.Connection -> VersionRangeChat -> User -> UTCTime -> IO [GroupMember]
-getRemovedMembersToCleanup db vr user@User {userId} cutoffTs =
- map (toContactMember vr user)
+getRemovedMembersToCleanup :: DB.Connection -> StoreCxt -> User -> UTCTime -> IO [GroupMember]
+getRemovedMembersToCleanup db cxt user@User {userId} cutoffTs =
+ map (toContactMember cxt user)
<$> DB.query
db
(groupMemberQuery <> " WHERE m.user_id = ? AND m.removed_at < ?")
(userId, cutoffTs)
-getGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> GroupId -> ExceptT StoreError IO ReceivedGroupInvitation
-getGroupInvitation db vr user groupId =
+getGroupInvitation :: DB.Connection -> StoreCxt -> User -> GroupId -> ExceptT StoreError IO ReceivedGroupInvitation
+getGroupInvitation db cxt user groupId =
getConnRec_ user >>= \case
Just connRequest -> do
- groupInfo@GroupInfo {membership} <- getGroupInfo db vr user groupId
+ groupInfo@GroupInfo {membership} <- getGroupInfo db cxt user groupId
when (memberStatus membership /= GSMemInvited) $ throwError SEGroupAlreadyJoined
hostId <- getHostMemberId_ db user groupId
- fromMember <- getGroupMember db vr user groupId hostId
+ fromMember <- getGroupMember db cxt user groupId hostId
pure ReceivedGroupInvitation {fromMember, connRequest, groupInfo}
_ -> throwError SEGroupInvitationNotFound
where
@@ -1413,8 +1413,8 @@ toGroupRelay ((groupRelayId, groupMemberId, chatRelayId, address, displayName, f
relayCap = RelayCapabilities {webDomain}
in GroupRelay {groupRelayId, groupMemberId, userChatRelay, relayStatus, relayLink, relayCap}
-createRelayForOwner :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> UserChatRelay -> ExceptT StoreError IO GroupMember
-createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {relayProfile = RelayProfile {displayName}} = do
+createRelayForOwner :: DB.Connection -> StoreCxt -> TVar ChaChaDRG -> User -> GroupInfo -> UserChatRelay -> ExceptT StoreError IO GroupMember
+createRelayForOwner db cxt gVar user@User {userId, userContactId} GroupInfo {groupId, membership} UserChatRelay {relayProfile = RelayProfile {displayName}} = do
currentTs <- liftIO getCurrentTime
let relayProfile = profileFromName displayName
(localDisplayName, memProfileId) <- createNewMemberProfile_ db user relayProfile currentTs
@@ -1433,14 +1433,14 @@ createRelayForOwner db vr gVar user@User {userId, userContactId} GroupInfo {grou
:. (userId, localDisplayName, memProfileId, currentTs, currentTs)
)
liftIO $ insertedRowId db
- getGroupMemberById db vr user groupMemberId
+ getGroupMemberById db cxt user groupMemberId
-getCreateRelayForMember :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupInfo -> ShortLinkContact -> ExceptT StoreError IO GroupMember
-getCreateRelayForMember db vr gVar user@User {userId, userContactId} GroupInfo {groupId, localDisplayName = groupLDN} relayLink =
+getCreateRelayForMember :: DB.Connection -> StoreCxt -> TVar ChaChaDRG -> User -> GroupInfo -> ShortLinkContact -> ExceptT StoreError IO GroupMember
+getCreateRelayForMember db cxt gVar user@User {userId, userContactId} GroupInfo {groupId, localDisplayName = groupLDN} relayLink =
liftIO getGroupMemberByRelayLink >>= maybe createRelayMember pure
where
getGroupMemberByRelayLink =
- maybeFirstRow (toContactMember vr user) $
+ maybeFirstRow (toContactMember cxt user) $
DB.query
db
#if defined(dbPostgres)
@@ -1471,10 +1471,10 @@ getCreateRelayForMember db vr gVar user@User {userId, userContactId} GroupInfo {
:. (userId, localDisplayName, profileId, currentTs, currentTs, relayLink)
)
insertedRowId db
- getGroupMember db vr user groupId groupMemberId
+ getGroupMember db cxt user groupId groupMemberId
-createRelayConnection :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ConnId -> ConnStatus -> VersionChat -> SubscriptionMode -> ExceptT StoreError IO Connection
-createRelayConnection db vr user@User {userId} groupMemberId agentConnId connStatus chatV subMode = do
+createRelayConnection :: DB.Connection -> StoreCxt -> User -> Int64 -> ConnId -> ConnStatus -> VersionChat -> SubscriptionMode -> ExceptT StoreError IO Connection
+createRelayConnection db cxt user@User {userId} groupMemberId agentConnId connStatus chatV subMode = do
currentTs <- liftIO getCurrentTime
liftIO $
DB.execute
@@ -1491,7 +1491,7 @@ createRelayConnection db vr user@User {userId} groupMemberId agentConnId connSta
:. (currentTs, currentTs)
)
connId <- liftIO $ insertedRowId db
- getConnectionById db vr user connId
+ getConnectionById db cxt user connId
updateRelayStatus :: DB.Connection -> GroupRelay -> RelayStatus -> IO GroupRelay
updateRelayStatus db relay@GroupRelay {groupRelayId} relayStatus =
@@ -1508,8 +1508,8 @@ updateRelayStatus_ db relayId relayStatus = do
currentTs <- getCurrentTime
DB.execute db "UPDATE group_relays SET relay_status = ?, updated_at = ? WHERE group_relay_id = ?" (relayStatus, currentTs, relayId)
-setRelayLinkAccepted :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> MemberKey -> Profile -> ExceptT StoreError IO (GroupMember, GroupRelay)
-setRelayLinkAccepted db vr user m (MemberKey relayKey) profile = do
+setRelayLinkAccepted :: DB.Connection -> StoreCxt -> User -> GroupMember -> MemberKey -> Profile -> ExceptT StoreError IO (GroupMember, GroupRelay)
+setRelayLinkAccepted db cxt user m (MemberKey relayKey) profile = do
let gmId = groupMemberId' m
currentTs <- liftIO getCurrentTime
liftIO $ DB.execute
@@ -1529,7 +1529,7 @@ setRelayLinkAccepted db vr user m (MemberKey relayKey) profile = do
|]
(relayKey, currentTs, gmId)
void $ updateMemberProfile db user m profile
- (,) <$> getGroupMemberById db vr user gmId <*> getGroupRelayByGMId db gmId
+ (,) <$> getGroupMemberById db cxt user gmId <*> getGroupRelayByGMId db gmId
setRelayLinkConfId :: DB.Connection -> GroupMember -> ConfirmationId -> ShortLinkContact -> IO ()
setRelayLinkConfId db m confId relayLink = do
@@ -1597,8 +1597,8 @@ setGroupInProgressDone db GroupInfo {groupId} = do
"UPDATE groups SET creating_in_progress = 0, updated_at = ? WHERE group_id = ?"
(currentTs, groupId)
-createRelayRequestGroup :: DB.Connection -> VersionRangeChat -> User -> GroupRelayInvitation -> InvitationId -> VersionRangeChat -> Int64 -> GroupMemberStatus -> RelayStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
-createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMember, fromMemberProfile, relayMemberId, groupLink} invId reqChatVRange initialDelay memberStatus relayStatus = do
+createRelayRequestGroup :: DB.Connection -> StoreCxt -> User -> GroupRelayInvitation -> InvitationId -> VersionRangeChat -> Int64 -> GroupMemberStatus -> RelayStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
+createRelayRequestGroup db cxt user@User {userId} GroupRelayInvitation {fromMember, fromMemberProfile, relayMemberId, groupLink} invId reqChatVRange initialDelay memberStatus relayStatus = do
currentTs <- liftIO getCurrentTime
-- Create group with placeholder profile
let Profile {displayName = fromMemberLDN} = fromMemberProfile
@@ -1619,9 +1619,9 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe
ownerMemberId <- insertOwner_ currentTs groupId
let relayMember = MemberIdRole relayMemberId GRRelay
-- TODO [member keys] should relays use member keys?
- _membership <- createContactMemberInv_ db user groupId (Just ownerMemberId) user relayMember GCUserMember memberStatus IBUnknown Nothing Nothing currentTs vr
- ownerMember <- getGroupMember db vr user groupId ownerMemberId
- g <- getGroupInfo db vr user groupId
+ _membership <- createContactMemberInv_ db user groupId (Just ownerMemberId) user relayMember GCUserMember memberStatus IBUnknown Nothing Nothing currentTs (vr cxt)
+ ownerMember <- getGroupMember db cxt user groupId ownerMemberId
+ g <- getGroupInfo db cxt user groupId
pure (g, ownerMember)
where
setRelayRequestData_ groupId currentTs =
@@ -1673,8 +1673,8 @@ updateRelayOwnStatus_ db GroupInfo {groupId} relayStatus = do
-- Flip every RSRejected row sharing the targeted group's relay_request_group_link
-- to RSInactive in one statement; returns the refreshed GroupInfo for the targeted groupId.
-allowRelayGroup :: DB.Connection -> VersionRangeChat -> User -> GroupId -> ExceptT StoreError IO GroupInfo
-allowRelayGroup db vr user@User {userId} groupId = do
+allowRelayGroup :: DB.Connection -> StoreCxt -> User -> GroupId -> ExceptT StoreError IO GroupInfo
+allowRelayGroup db cxt user@User {userId} groupId = do
currentTs <- liftIO getCurrentTime
liftIO $
DB.execute
@@ -1687,7 +1687,7 @@ allowRelayGroup db vr user@User {userId} groupId = do
AND relay_own_status = ?
|]
(RSInactive, currentTs, currentTs, userId, groupId, RSRejected)
- getGroupInfo db vr user groupId
+ getGroupInfo db cxt user groupId
isRelayGroupRejected :: DB.Connection -> User -> ShortLinkContact -> IO Bool
isRelayGroupRejected db User {userId} groupLink =
@@ -1706,9 +1706,9 @@ isRelayGroupRejected db User {userId} groupLink =
(userId, groupLink, RSRejected)
)
-getRelayServedGroups :: DB.Connection -> VersionRangeChat -> User -> IO [GroupInfo]
-getRelayServedGroups db vr User {userId, userContactId} = do
- map (toGroupInfo vr userContactId [])
+getRelayServedGroups :: DB.Connection -> StoreCxt -> User -> IO [GroupInfo]
+getRelayServedGroups db cxt User {userId, userContactId} = do
+ map (toGroupInfo cxt userContactId [])
<$> DB.query
db
( groupInfoQuery
@@ -1716,10 +1716,10 @@ getRelayServedGroups db vr User {userId, userContactId} = do
)
(userId, userContactId, RSAccepted, RSActive)
-getRelayInactiveGroups :: DB.Connection -> VersionRangeChat -> User -> NominalDiffTime -> IO [GroupInfo]
-getRelayInactiveGroups db vr User {userId, userContactId} ttl = do
+getRelayInactiveGroups :: DB.Connection -> StoreCxt -> User -> NominalDiffTime -> IO [GroupInfo]
+getRelayInactiveGroups db cxt User {userId, userContactId} ttl = do
cutoffTs <- addUTCTime (- ttl) <$> getCurrentTime
- map (toGroupInfo vr userContactId [])
+ map (toGroupInfo cxt userContactId [])
<$> DB.query
db
( groupInfoQuery
@@ -1831,10 +1831,10 @@ createJoiningMemberConnection
Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId ConnNew chatV cReqChatVRange Nothing (Just uclId) Nothing 0 createdAt subMode PQSupportOff Nothing
setCommandConnId db user cmdId connId
-createBusinessRequestGroup :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> VersionRangeChat -> Profile -> Int64 -> Text -> GroupPreferences -> ExceptT StoreError IO (GroupInfo, GroupMember)
+createBusinessRequestGroup :: DB.Connection -> StoreCxt -> TVar ChaChaDRG -> User -> VersionRangeChat -> Profile -> Int64 -> Text -> GroupPreferences -> ExceptT StoreError IO (GroupInfo, GroupMember)
createBusinessRequestGroup
db
- vr
+ cxt
gVar
user@User {userId, userContactId}
cReqChatVRange
@@ -1846,8 +1846,8 @@ createBusinessRequestGroup
(groupId, membership@GroupMember {memberId = userMemberId}) <- insertGroup_ currentTs
(groupMemberId, memberId) <- insertClientMember_ currentTs groupId membership
liftIO $ DB.execute db "UPDATE groups SET business_member_id = ?, customer_member_id = ? WHERE group_id = ?" (userMemberId, memberId, groupId)
- groupInfo <- getGroupInfo db vr user groupId
- clientMember <- getGroupMemberById db vr user groupMemberId
+ groupInfo <- getGroupInfo db cxt user groupId
+ clientMember <- getGroupMemberById db cxt user groupMemberId
pure (groupInfo, clientMember)
where
insertGroup_ currentTs = do
@@ -1870,7 +1870,7 @@ createBusinessRequestGroup
groupId <- liftIO $ insertedRowId db
memberId <- liftIO $ encodedRandomBytes gVar 12
-- TODO [member keys] we could support member keys in business groups to allow binding agreements (though identity keys would be better for it.
- membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing Nothing currentTs vr
+ membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing Nothing currentTs (vr cxt)
pure (groupId, membership)
VersionRange minV maxV = cReqChatVRange
insertClientMember_ currentTs groupId membership =
@@ -1894,8 +1894,8 @@ createBusinessRequestGroup
groupMemberId <- liftIO $ insertedRowId db
pure (groupMemberId, MemberId memId)
-getContactViaMember :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> ExceptT StoreError IO Contact
-getContactViaMember db vr user@User {userId} GroupMember {groupMemberId} = do
+getContactViaMember :: DB.Connection -> StoreCxt -> User -> GroupMember -> ExceptT StoreError IO Contact
+getContactViaMember db cxt user@User {userId} GroupMember {groupMemberId} = do
contactId <-
ExceptT $
firstRow fromOnly (SEContactNotFoundByMemberId groupMemberId) $
@@ -1909,7 +1909,7 @@ getContactViaMember db vr user@User {userId} GroupMember {groupMemberId} = do
LIMIT 1
|]
(userId, groupMemberId)
- getContact db vr user contactId
+ getContact db cxt user contactId
setNewContactMemberConnRequest :: DB.Connection -> User -> GroupMember -> ConnReqInvitation -> IO ()
setNewContactMemberConnRequest db User {userId} GroupMember {groupMemberId} connRequest = do
@@ -1936,18 +1936,18 @@ createMemberConnectionAsync db user@User {userId} groupMemberId (cmdId, agentCon
-- This is called once before connecting to relays, unlike createConnReqConnection -> setPreparedGroupLinkInfo_,
-- which is used in single-connection flows.
updatePreparedRelayedGroup ::
- DB.Connection -> VersionRangeChat -> User -> GroupInfo -> ConnReqContact -> ConnReqUriHash -> Maybe Profile ->
+ DB.Connection -> StoreCxt -> User -> GroupInfo -> ConnReqContact -> ConnReqUriHash -> Maybe Profile ->
C.PublicKeyEd25519 -> C.PrivateKeyEd25519 -> Maybe Int64 ->
ExceptT StoreError IO GroupInfo
-updatePreparedRelayedGroup db vr user@User {userId} gInfo cReq cReqHash incognitoProfile rootPubKey memberPrivKey publicMemberCount_ = do
+updatePreparedRelayedGroup db cxt user@User {userId} gInfo cReq cReqHash incognitoProfile rootPubKey memberPrivKey publicMemberCount_ = do
currentTs <- liftIO getCurrentTime
customUserProfileId <- liftIO $ mapM (createIncognitoProfile_ db userId currentTs) incognitoProfile
liftIO $ setPreparedGroupLinkInfo_ db gInfo cReq cReqHash customUserProfileId publicMemberCount_ currentTs
liftIO $ updateGroupMemberKeys db (groupId' gInfo) rootPubKey memberPrivKey (groupMemberId' $ membership gInfo)
- getGroupInfo db vr user (groupId' gInfo)
+ getGroupInfo db cxt user (groupId' gInfo)
-updatePublicMemberCount :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> ExceptT StoreError IO GroupInfo
-updatePublicMemberCount db vr user GroupInfo {groupId} = do
+updatePublicMemberCount :: DB.Connection -> StoreCxt -> User -> GroupInfo -> ExceptT StoreError IO GroupInfo
+updatePublicMemberCount db cxt user GroupInfo {groupId} = do
liftIO $ do
totalCount <- fromMaybe 0 <$> maybeFirstRow fromOnly
(DB.query db "SELECT summary_current_members_count FROM groups WHERE group_id = ?" (Only groupId))
@@ -1963,13 +1963,13 @@ updatePublicMemberCount db vr user GroupInfo {groupId} = do
let publicCount = max 0 (totalCount - relayCount) :: Int64
currentTs <- getCurrentTime
DB.execute db "UPDATE groups SET public_member_count = ?, updated_at = ? WHERE group_id = ?" (publicCount, currentTs, groupId)
- getGroupInfo db vr user groupId
+ getGroupInfo db cxt user groupId
-setPublicMemberCount :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Int64 -> ExceptT StoreError IO GroupInfo
-setPublicMemberCount db vr user GroupInfo {groupId} publicCount = do
+setPublicMemberCount :: DB.Connection -> StoreCxt -> User -> GroupInfo -> Int64 -> ExceptT StoreError IO GroupInfo
+setPublicMemberCount db cxt user GroupInfo {groupId} publicCount = do
currentTs <- liftIO getCurrentTime
liftIO $ DB.execute db "UPDATE groups SET public_member_count = ?, updated_at = ? WHERE group_id = ?" (publicCount, currentTs, groupId)
- getGroupInfo db vr user groupId
+ getGroupInfo db cxt user groupId
updateGroupMemberKeys :: DB.Connection -> GroupId -> C.PublicKeyEd25519 -> C.PrivateKeyEd25519 -> GroupMemberId -> IO ()
updateGroupMemberKeys db groupId rootPubKey memberPrivKey membershipGMId = do
@@ -2539,8 +2539,8 @@ updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName
let publicGroupAccess = toPublicGroupAccess accessRow
in GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup = toPublicGroupProfile groupType_ groupLink_ publicGroupId_ publicGroupAccess, simplexName = decodeSimplexName simplexNameRaw, groupPreferences, memberAdmission}
-getGroupInfoByUserContactLinkConnReq :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo)
-getGroupInfoByUserContactLinkConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2) = do
+getGroupInfoByUserContactLinkConnReq :: DB.Connection -> StoreCxt -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo)
+getGroupInfoByUserContactLinkConnReq db cxt user@User {userId} (cReqSchema1, cReqSchema2) = do
-- fmap join is to support group_id = NULL if non-group contact request is sent to this function (e.g., if client data is appended).
groupId_ <-
fmap join . maybeFirstRow fromOnly $
@@ -2552,12 +2552,12 @@ getGroupInfoByUserContactLinkConnReq db vr user@User {userId} (cReqSchema1, cReq
WHERE user_id = ? AND conn_req_contact IN (?,?)
|]
(userId, cReqSchema1, cReqSchema2)
- maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db vr user) groupId_
+ maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db cxt user) groupId_
-getGroupInfoViaUserShortLink :: DB.Connection -> VersionRangeChat -> User -> ShortLinkContact -> IO (Maybe (ConnReqContact, GroupInfo))
-getGroupInfoViaUserShortLink db vr user@User {userId} shortLink = fmap eitherToMaybe $ runExceptT $ do
+getGroupInfoViaUserShortLink :: DB.Connection -> StoreCxt -> User -> ShortLinkContact -> IO (Maybe (ConnReqContact, GroupInfo))
+getGroupInfoViaUserShortLink db cxt user@User {userId} shortLink = fmap eitherToMaybe $ runExceptT $ do
(cReq, groupId) <- ExceptT getConnReqGroup
- (cReq,) <$> getGroupInfo db vr user groupId
+ (cReq,) <$> getGroupInfo db cxt user groupId
where
getConnReqGroup =
firstRow' toConnReqGroupId (SEInternalError "group link not found") $
@@ -2574,14 +2574,14 @@ getGroupInfoViaUserShortLink db vr user@User {userId} shortLink = fmap eitherToM
(cReq, Just groupId) -> Right (cReq, groupId)
_ -> Left $ SEInternalError "no conn req or group ID"
-getGroupViaShortLinkToConnect :: DB.Connection -> VersionRangeChat -> User -> ShortLinkContact -> ExceptT StoreError IO (Maybe (ConnReqContact, GroupInfo))
-getGroupViaShortLinkToConnect db vr user@User {userId} shortLink =
+getGroupViaShortLinkToConnect :: DB.Connection -> StoreCxt -> User -> ShortLinkContact -> ExceptT StoreError IO (Maybe (ConnReqContact, GroupInfo))
+getGroupViaShortLinkToConnect db cxt user@User {userId} shortLink =
liftIO (maybeFirstRow id $ DB.query db "SELECT group_id, conn_full_link_to_connect FROM groups WHERE user_id = ? AND conn_short_link_to_connect = ?" (userId, shortLink)) >>= \case
- Just (gId :: Int64, Just cReq) -> Just . (cReq,) <$> getGroupInfo db vr user gId
+ Just (gId :: Int64, Just cReq) -> Just . (cReq,) <$> getGroupInfo db cxt user gId
_ -> pure Nothing
-getGroupInfoByGroupLinkHash :: DB.Connection -> VersionRangeChat -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe GroupInfo)
-getGroupInfoByGroupLinkHash db vr user@User {userId, userContactId} (groupLinkHash1, groupLinkHash2) = do
+getGroupInfoByGroupLinkHash :: DB.Connection -> StoreCxt -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe GroupInfo)
+getGroupInfoByGroupLinkHash db cxt user@User {userId, userContactId} (groupLinkHash1, groupLinkHash2) = do
groupId_ <-
maybeFirstRow fromOnly $
DB.query
@@ -2595,7 +2595,7 @@ getGroupInfoByGroupLinkHash db vr user@User {userId, userContactId} (groupLinkHa
LIMIT 1
|]
(userId, groupLinkHash1, groupLinkHash2, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted, GSMemUnknown)
- maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db vr user) groupId_
+ maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db cxt user) groupId_
getGroupIdByName :: DB.Connection -> User -> GroupName -> ExceptT StoreError IO GroupId
getGroupIdByName db User {userId} gName =
@@ -2607,8 +2607,8 @@ getGroupMemberIdByName db User {userId} groupId groupMemberName =
ExceptT . firstRow fromOnly (SEGroupMemberNameNotFound groupId groupMemberName) $
DB.query db "SELECT group_member_id FROM group_members WHERE user_id = ? AND group_id = ? AND local_display_name = ?" (userId, groupId, groupMemberName)
-getActiveMembersByName :: DB.Connection -> VersionRangeChat -> User -> ContactName -> ExceptT StoreError IO [(GroupInfo, GroupMember)]
-getActiveMembersByName db vr user@User {userId} groupMemberName = do
+getActiveMembersByName :: DB.Connection -> StoreCxt -> User -> ContactName -> ExceptT StoreError IO [(GroupInfo, GroupMember)]
+getActiveMembersByName db cxt user@User {userId} groupMemberName = do
groupMemberIds :: [(GroupId, GroupMemberId)] <-
liftIO $
DB.query
@@ -2621,17 +2621,17 @@ getActiveMembersByName db vr user@User {userId} groupMemberName = do
|]
(userId, groupMemberName, GSMemConnected, GSMemComplete, GCUserMember)
possibleMembers <- forM groupMemberIds $ \(groupId, groupMemberId) -> do
- groupInfo <- getGroupInfo db vr user groupId
- groupMember <- getGroupMember db vr user groupId groupMemberId
+ groupInfo <- getGroupInfo db cxt user groupId
+ groupMember <- getGroupMember db cxt user groupId groupMemberId
pure (groupInfo, groupMember)
pure $ sortOn (Down . ts . fst) possibleMembers
where
ts GroupInfo {chatTs, updatedAt} = fromMaybe updatedAt chatTs
-getMatchingContacts :: DB.Connection -> VersionRangeChat -> User -> Contact -> IO [Contact]
-getMatchingContacts db vr user@User {userId} Contact {contactId, profile = LocalProfile {displayName, fullName, shortDescr, image}} = do
+getMatchingContacts :: DB.Connection -> StoreCxt -> User -> Contact -> IO [Contact]
+getMatchingContacts db cxt user@User {userId} Contact {contactId, profile = LocalProfile {displayName, fullName, shortDescr, image}} = do
contactIds <- map fromOnly <$> DB.query db q (userId, contactId, CSActive, displayName, fullName, shortDescr, image)
- rights <$> mapM (runExceptT . getContact db vr user) contactIds
+ rights <$> mapM (runExceptT . getContact db cxt user) contactIds
where
-- this query is different from one in getMatchingMemberContacts
-- it checks that it's not the same contact
@@ -2646,10 +2646,10 @@ getMatchingContacts db vr user@User {userId} Contact {contactId, profile = Local
AND p.short_descr IS NOT DISTINCT FROM ? AND p.image IS NOT DISTINCT FROM ?
|]
-getMatchingMembers :: DB.Connection -> VersionRangeChat -> User -> Contact -> IO [GroupMember]
-getMatchingMembers db vr user@User {userId} Contact {profile = LocalProfile {displayName, fullName, shortDescr, image}} = do
+getMatchingMembers :: DB.Connection -> StoreCxt -> User -> Contact -> IO [GroupMember]
+getMatchingMembers db cxt user@User {userId} Contact {profile = LocalProfile {displayName, fullName, shortDescr, image}} = do
memberIds <- map fromOnly <$> DB.query db q (userId, GCUserMember, displayName, fullName, shortDescr, image)
- filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db vr user) memberIds
+ filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db cxt user) memberIds
where
-- only match with members without associated contact
q =
@@ -2663,11 +2663,11 @@ getMatchingMembers db vr user@User {userId} Contact {profile = LocalProfile {dis
AND p.short_descr IS NOT DISTINCT FROM ? AND p.image IS NOT DISTINCT FROM ?
|]
-getMatchingMemberContacts :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> IO [Contact]
+getMatchingMemberContacts :: DB.Connection -> StoreCxt -> User -> GroupMember -> IO [Contact]
getMatchingMemberContacts _ _ _ GroupMember {memberContactId = Just _} = pure []
-getMatchingMemberContacts db vr user@User {userId} GroupMember {memberProfile = LocalProfile {displayName, fullName, shortDescr, image}} = do
+getMatchingMemberContacts db cxt user@User {userId} GroupMember {memberProfile = LocalProfile {displayName, fullName, shortDescr, image}} = do
contactIds <- map fromOnly <$> DB.query db q (userId, CSActive, displayName, fullName, shortDescr, image)
- rights <$> mapM (runExceptT . getContact db vr user) contactIds
+ rights <$> mapM (runExceptT . getContact db cxt user) contactIds
where
q =
[sql|
@@ -2700,8 +2700,8 @@ createSentProbeHash db userId probeId to = do
"INSERT INTO sent_probe_hashes (sent_probe_id, contact_id, group_member_id, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?)"
(probeId, ctId, gmId, userId, currentTs, currentTs)
-matchReceivedProbe :: DB.Connection -> VersionRangeChat -> User -> ContactOrMember -> Probe -> IO [ContactOrMember]
-matchReceivedProbe db vr user@User {userId} from (Probe probe) = do
+matchReceivedProbe :: DB.Connection -> StoreCxt -> User -> ContactOrMember -> Probe -> IO [ContactOrMember]
+matchReceivedProbe db cxt user@User {userId} from (Probe probe) = do
let probeHash = C.sha256Hash probe
cgmIds <-
DB.query
@@ -2722,7 +2722,7 @@ matchReceivedProbe db vr user@User {userId} from (Probe probe) = do
"INSERT INTO received_probes (contact_id, group_member_id, probe, probe_hash, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?)"
(ctId, gmId, Binary probe, Binary probeHash, userId, currentTs, currentTs)
let cgmIds' = filterFirstContactId cgmIds
- catMaybes <$> mapM (getContactOrMember_ db vr user) cgmIds'
+ catMaybes <$> mapM (getContactOrMember_ db cxt user) cgmIds'
where
filterFirstContactId :: [(Maybe ContactId, Maybe GroupId, Maybe GroupMemberId)] -> [(Maybe ContactId, Maybe GroupId, Maybe GroupMemberId)]
filterFirstContactId cgmIds = do
@@ -2732,8 +2732,8 @@ matchReceivedProbe db vr user@User {userId} from (Probe probe) = do
(x : _) -> [x]
ctIds' <> memIds
-matchReceivedProbeHash :: DB.Connection -> VersionRangeChat -> User -> ContactOrMember -> ProbeHash -> IO (Maybe (ContactOrMember, Probe))
-matchReceivedProbeHash db vr user@User {userId} from (ProbeHash probeHash) = do
+matchReceivedProbeHash :: DB.Connection -> StoreCxt -> User -> ContactOrMember -> ProbeHash -> IO (Maybe (ContactOrMember, Probe))
+matchReceivedProbeHash db cxt user@User {userId} from (ProbeHash probeHash) = do
probeIds <-
maybeFirstRow id $
DB.query
@@ -2753,11 +2753,11 @@ matchReceivedProbeHash db vr user@User {userId} from (ProbeHash probeHash) = do
db
"INSERT INTO received_probes (contact_id, group_member_id, probe_hash, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?)"
(ctId, gmId, Binary probeHash, userId, currentTs, currentTs)
- pure probeIds $>>= \(Only probe :. cgmIds) -> (,Probe probe) <$$> getContactOrMember_ db vr user cgmIds
+ pure probeIds $>>= \(Only probe :. cgmIds) -> (,Probe probe) <$$> getContactOrMember_ db cxt user cgmIds
-matchSentProbe :: DB.Connection -> VersionRangeChat -> User -> ContactOrMember -> Probe -> IO (Maybe ContactOrMember)
-matchSentProbe db vr user@User {userId} _from (Probe probe) = do
- cgmIds $>>= getContactOrMember_ db vr user
+matchSentProbe :: DB.Connection -> StoreCxt -> User -> ContactOrMember -> Probe -> IO (Maybe ContactOrMember)
+matchSentProbe db cxt user@User {userId} _from (Probe probe) = do
+ cgmIds $>>= getContactOrMember_ db cxt user
where
(ctId, gmId) = contactOrMemberIds _from
cgmIds =
@@ -2776,11 +2776,11 @@ matchSentProbe db vr user@User {userId} _from (Probe probe) = do
|]
(userId, Binary probe, ctId, gmId)
-getContactOrMember_ :: DB.Connection -> VersionRangeChat -> User -> (Maybe ContactId, Maybe GroupId, Maybe GroupMemberId) -> IO (Maybe ContactOrMember)
-getContactOrMember_ db vr user ids =
+getContactOrMember_ :: DB.Connection -> StoreCxt -> User -> (Maybe ContactId, Maybe GroupId, Maybe GroupMemberId) -> IO (Maybe ContactOrMember)
+getContactOrMember_ db cxt user ids =
fmap eitherToMaybe . runExceptT $ case ids of
- (Just ctId, _, _) -> COMContact <$> getContact db vr user ctId
- (_, Just gId, Just gmId) -> COMGroupMember <$> getGroupMember db vr user gId gmId
+ (Just ctId, _, _) -> COMContact <$> getContact db cxt user ctId
+ (_, Just gId, Just gmId) -> COMGroupMember <$> getGroupMember db cxt user gId gmId
_ -> throwError $ SEInternalError ""
associateMemberWithContactRecord :: DB.Connection -> User -> Contact -> GroupMember -> IO ()
@@ -2801,10 +2801,10 @@ associateMemberWithContactRecord
when (memProfileId /= profileId) $ deleteUnusedProfile_ db userId memProfileId
when (memLDN /= localDisplayName) $ deleteUnusedDisplayName_ db userId memLDN
-associateContactWithMemberRecord :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> Contact -> ExceptT StoreError IO Contact
+associateContactWithMemberRecord :: DB.Connection -> StoreCxt -> User -> GroupMember -> Contact -> ExceptT StoreError IO Contact
associateContactWithMemberRecord
db
- vr
+ cxt
user@User {userId}
GroupMember {groupId, groupMemberId, localDisplayName = memLDN, memberProfile = LocalProfile {profileId = memProfileId}}
Contact {contactId, localDisplayName, profile = LocalProfile {profileId}} = do
@@ -2828,7 +2828,7 @@ associateContactWithMemberRecord
(memLDN, memProfileId, currentTs, userId, contactId)
when (profileId /= memProfileId) $ deleteUnusedProfile_ db userId profileId
when (localDisplayName /= memLDN) $ deleteUnusedDisplayName_ db userId localDisplayName
- getContact db vr user contactId
+ getContact db cxt user contactId
deleteUnusedDisplayName_ :: DB.Connection -> UserId -> ContactName -> IO ()
deleteUnusedDisplayName_ db userId localDisplayName =
@@ -2985,15 +2985,15 @@ createMemberContact
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn
pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, preparedContact = Nothing, contactRequestId = Nothing, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, groupDirectInv = Nothing, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing, simplexName = Nothing, simplexNameVerifiedAt = Nothing}
-getMemberContact :: DB.Connection -> VersionRangeChat -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation)
-getMemberContact db vr user contactId = do
- ct <- getContact db vr user contactId
+getMemberContact :: DB.Connection -> StoreCxt -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation)
+getMemberContact db cxt user contactId = do
+ ct <- getContact db cxt user contactId
let Contact {contactGroupMemberId, activeConn} = ct
case (activeConn, contactGroupMemberId) of
(Just Connection {connId}, Just groupMemberId) -> do
cReq <- getConnReqInv db connId
- m@GroupMember {groupId} <- getGroupMemberById db vr user groupMemberId
- g <- getGroupInfo db vr user groupId
+ m@GroupMember {groupId} <- getGroupMemberById db cxt user groupMemberId
+ g <- getGroupInfo db cxt user groupId
pure (g, m, ct, cReq)
_ ->
throwError $ SEMemberContactGroupMemberNotFound contactId
@@ -3102,13 +3102,13 @@ createMemberContactConn
forM_ cmdId_ $ \cmdId -> setCommandConnId db user cmdId connId
pure connId
-getMemberContactInvited :: DB.Connection -> VersionRangeChat -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, Connection, Contact, GroupDirectInvitation)
-getMemberContactInvited db vr user contactId = do
- ct@Contact {groupDirectInv = groupDirectInv_} <- getContact db vr user contactId
+getMemberContactInvited :: DB.Connection -> StoreCxt -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, Connection, Contact, GroupDirectInvitation)
+getMemberContactInvited db cxt user contactId = do
+ ct@Contact {groupDirectInv = groupDirectInv_} <- getContact db cxt user contactId
case groupDirectInv_ of
Just groupDirectInv@GroupDirectInvitation {fromGroupId_ = Just groupId, fromGroupMemberId_ = Just _gmId, fromGroupMemberConnId_ = Just mConnId} -> do
- g <- getGroupInfo db vr user groupId
- mConn <- getConnectionById db vr user mConnId
+ g <- getGroupInfo db cxt user groupId
+ mConn <- getConnectionById db cxt user mConnId
pure (g, mConn, ct, groupDirectInv)
_ ->
throwError $ SEMemberContactGroupMemberNotFound contactId
@@ -3190,8 +3190,8 @@ setXGrpLinkMemReceived db mId xGrpLinkMemReceived = do
"UPDATE group_members SET xgrplinkmem_received = ?, updated_at = ? WHERE group_member_id = ?"
(BI xGrpLinkMemReceived, currentTs, mId)
-createNewUnknownGroupMember :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> Text -> GroupMemberRole -> ExceptT StoreError IO GroupMember
-createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {groupId} memberId memberName unknownMemberRole = do
+createNewUnknownGroupMember :: DB.Connection -> StoreCxt -> User -> GroupInfo -> MemberId -> Text -> GroupMemberRole -> ExceptT StoreError IO GroupMember
+createNewUnknownGroupMember db cxt user@User {userId, userContactId} GroupInfo {groupId} memberId memberName unknownMemberRole = do
currentTs <- liftIO getCurrentTime
let memberProfile = profileFromName memberName
(localDisplayName, profileId) <- createNewMemberProfile_ db user memberProfile currentTs
@@ -3211,12 +3211,12 @@ createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {g
:. (minV, maxV)
)
groupMemberId <- liftIO $ insertedRowId db
- getGroupMemberById db vr user groupMemberId
+ getGroupMemberById db cxt user groupMemberId
where
- VersionRange minV maxV = vr
+ VersionRange minV maxV = vr cxt
-createLinkOwnerMember :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe ContactId -> MemberId -> C.PublicKeyEd25519 -> ExceptT StoreError IO GroupMember
-createLinkOwnerMember db vr user@User {userId, userContactId} GroupInfo {groupId} contactId_ memberId ownerKey = do
+createLinkOwnerMember :: DB.Connection -> StoreCxt -> User -> GroupInfo -> Maybe ContactId -> MemberId -> C.PublicKeyEd25519 -> ExceptT StoreError IO GroupMember
+createLinkOwnerMember db cxt user@User {userId, userContactId} GroupInfo {groupId} contactId_ memberId ownerKey = do
currentTs <- liftIO getCurrentTime
let memberProfile = profileFromName $ nameFromMemberId memberId
(localDisplayName, profileId) <- createNewMemberProfile_ db user memberProfile currentTs
@@ -3236,15 +3236,15 @@ createLinkOwnerMember db vr user@User {userId, userContactId} GroupInfo {groupId
:. (minV, maxV)
)
groupMemberId <- liftIO $ insertedRowId db
- getGroupMemberById db vr user groupMemberId
+ getGroupMemberById db cxt user groupMemberId
where
- VersionRange minV maxV = vr
+ VersionRange minV maxV = vr cxt
-- member_pub_key is not updated here — introduced members are owners
-- whose keys are loaded from link data (trusted out-of-band).
-- Updating from an in-band message would allow a compromised relay to substitute keys.
-updatePreparedChannelMember :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> MemberInfo -> ExceptT StoreError IO GroupMember
-updatePreparedChannelMember db vr user@User {userId} member@GroupMember {groupMemberId, memberChatVRange} MemberInfo {memberRole, v, profile} = do
+updatePreparedChannelMember :: DB.Connection -> StoreCxt -> User -> GroupMember -> MemberInfo -> ExceptT StoreError IO GroupMember
+updatePreparedChannelMember db cxt user@User {userId} member@GroupMember {groupMemberId, memberChatVRange} MemberInfo {memberRole, v, profile} = do
_ <- updateMemberProfile db user member profile
currentTs <- liftIO getCurrentTime
liftIO $
@@ -3260,12 +3260,12 @@ updatePreparedChannelMember db vr user@User {userId} member@GroupMember {groupMe
WHERE user_id = ? AND group_member_id = ?
|]
(memberRole, GSMemIntroduced, minV, maxV, currentTs, userId, groupMemberId)
- getGroupMemberById db vr user groupMemberId
+ getGroupMemberById db cxt user groupMemberId
where
VersionRange minV maxV = maybe memberChatVRange fromChatVRange v
-updateUnknownMemberAnnounced :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> GroupMember -> MemberInfo -> GroupMemberStatus -> ExceptT StoreError IO GroupMember
-updateUnknownMemberAnnounced db vr user@User {userId} invitingMember unknownMember@GroupMember {groupMemberId, memberChatVRange} MemberInfo {memberRole, v, profile, memberKey} status = do
+updateUnknownMemberAnnounced :: DB.Connection -> StoreCxt -> User -> GroupMember -> GroupMember -> MemberInfo -> GroupMemberStatus -> ExceptT StoreError IO GroupMember
+updateUnknownMemberAnnounced db cxt user@User {userId} invitingMember unknownMember@GroupMember {groupMemberId, memberChatVRange} MemberInfo {memberRole, v, profile, memberKey} status = do
_ <- updateMemberProfile db user unknownMember profile
currentTs <- liftIO getCurrentTime
liftIO $
@@ -3286,7 +3286,7 @@ updateUnknownMemberAnnounced db vr user@User {userId} invitingMember unknownMemb
( (memberRole, GCPostMember, status, groupMemberId' invitingMember)
:. (minV, maxV, memberPubKey_, currentTs, userId, groupMemberId)
)
- getGroupMemberById db vr user groupMemberId
+ getGroupMemberById db cxt user groupMemberId
where
VersionRange minV maxV = maybe memberChatVRange fromChatVRange v
memberPubKey_ = (\(MemberKey k) -> k) <$> memberKey
diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs
index 20657a96a7..ba20f1164c 100644
--- a/src/Simplex/Chat/Store/Messages.hs
+++ b/src/Simplex/Chat/Store/Messages.hs
@@ -406,8 +406,8 @@ data MemberAttention
| MAReset
deriving (Show)
-updateChatTsStats :: DB.Connection -> VersionRangeChat -> User -> ChatDirection c d -> UTCTime -> Maybe (Int, MemberAttention, Int) -> IO (ChatInfo c)
-updateChatTsStats db vr user@User {userId} chatDirection chatTs chatStats_ = case toChatInfo chatDirection of
+updateChatTsStats :: DB.Connection -> StoreCxt -> User -> ChatDirection c d -> UTCTime -> Maybe (Int, MemberAttention, Int) -> IO (ChatInfo c)
+updateChatTsStats db cxt user@User {userId} chatDirection chatTs chatStats_ = case toChatInfo chatDirection of
DirectChat ct@Contact {contactId} -> do
DB.execute
db
@@ -516,7 +516,7 @@ updateChatTsStats db vr user@User {userId} chatDirection chatTs chatStats_ = cas
WHERE group_member_id = ?
|]
(chatTs, unread, mentions, groupMemberId)
- m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId
+ m_ <- runExceptT $ getGroupMemberById db cxt user groupMemberId
pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
LocalChat nf@NoteFolder {noteFolderId} -> do
DB.execute
@@ -530,8 +530,8 @@ setSupportChatTs :: DB.Connection -> GroupMemberId -> UTCTime -> IO ()
setSupportChatTs db groupMemberId chatTs =
DB.execute db "UPDATE group_members SET support_chat_ts = ? WHERE group_member_id = ?" (chatTs, groupMemberId)
-setSupportChatMemberAttention :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> Int64 -> IO (GroupInfo, GroupMember)
-setSupportChatMemberAttention db vr user g m memberAttention = do
+setSupportChatMemberAttention :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupMember -> Int64 -> IO (GroupInfo, GroupMember)
+setSupportChatMemberAttention db cxt user g m memberAttention = do
m' <- updateGMAttention
g' <- updateGroupMembersRequireAttention db user g m m'
pure (g', m')
@@ -542,7 +542,7 @@ setSupportChatMemberAttention db vr user g m memberAttention = do
db
"UPDATE group_members SET support_chat_items_member_attention = ?, updated_at = ? WHERE group_member_id = ?"
(memberAttention, currentTs, groupMemberId' m)
- m_ <- runExceptT $ getGroupMemberById db vr user (groupMemberId' m)
+ m_ <- runExceptT $ getGroupMemberById db cxt user (groupMemberId' m)
pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
createNewSndChatItem :: DB.Connection -> User -> ChatDirection c 'MDSnd -> ShowGroupAsSender -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> UTCTime -> IO ChatItemId
@@ -733,8 +733,8 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe
ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing
ciQuoteGroup ((Only itemId :. memberRow) : _) = ciQuote itemId . CIQGroupRcv . Just $ toGroupMember userContactId memberRow
-getChatPreviews :: DB.Connection -> VersionRangeChat -> User -> Bool -> PaginationByTime -> ChatListQuery -> IO [Either StoreError AChat]
-getChatPreviews db vr user withPCC pagination query = do
+getChatPreviews :: DB.Connection -> StoreCxt -> User -> Bool -> PaginationByTime -> ChatListQuery -> IO [Either StoreError AChat]
+getChatPreviews db cxt user withPCC pagination query = do
directChats <- findDirectChatPreviews_ db user pagination query
groupChats <- findGroupChatPreviews_ db user pagination query
localChats <- findLocalChatPreviews_ db user pagination query
@@ -756,8 +756,8 @@ getChatPreviews db vr user withPCC pagination query = do
PTBefore _ count -> take count . sortBy (comparing $ Down . ts)
getChatPreview :: AChatPreviewData -> ExceptT StoreError IO AChat
getChatPreview (ACPD cType cpd) = case cType of
- SCTDirect -> getDirectChatPreview_ db vr user cpd
- SCTGroup -> getGroupChatPreview_ db vr user cpd
+ SCTDirect -> getDirectChatPreview_ db cxt user cpd
+ SCTGroup -> getGroupChatPreview_ db cxt user cpd
SCTLocal -> getLocalChatPreview_ db user cpd
SCTContactRequest -> let (ContactRequestPD _ chat) = cpd in pure chat
SCTContactConnection -> let (ContactConnectionPD _ chat) = cpd in pure chat
@@ -874,9 +874,9 @@ findDirectChatPreviews_ db User {userId} pagination clq =
PTAfter ts count -> DB.query db (query <> " AND ct.chat_ts > ? ORDER BY ct.chat_ts ASC LIMIT ?") (params :. (ts, count))
PTBefore ts count -> DB.query db (query <> " AND ct.chat_ts < ? ORDER BY ct.chat_ts DESC LIMIT ?") (params :. (ts, count))
-getDirectChatPreview_ :: DB.Connection -> VersionRangeChat -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat
-getDirectChatPreview_ db vr user (DirectChatPD _ contactId lastItemId_ stats) = do
- contact <- getContact db vr user contactId
+getDirectChatPreview_ :: DB.Connection -> StoreCxt -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat
+getDirectChatPreview_ db cxt user (DirectChatPD _ contactId lastItemId_ stats) = do
+ contact <- getContact db cxt user contactId
ts <- liftIO getCurrentTime
lastItem <- case lastItemId_ of
Just lastItemId -> do
@@ -985,9 +985,9 @@ findGroupChatPreviews_ db User {userId} pagination clq =
PTAfter ts count -> DB.query db (query <> " AND g.chat_ts > ? ORDER BY g.chat_ts ASC LIMIT ?") (params :. (ts, count))
PTBefore ts count -> DB.query db (query <> " AND g.chat_ts < ? ORDER BY g.chat_ts DESC LIMIT ?") (params :. (ts, count))
-getGroupChatPreview_ :: DB.Connection -> VersionRangeChat -> User -> ChatPreviewData 'CTGroup -> ExceptT StoreError IO AChat
-getGroupChatPreview_ db vr user (GroupChatPD _ groupId lastItemId_ stats) = do
- groupInfo <- getGroupInfo db vr user groupId
+getGroupChatPreview_ :: DB.Connection -> StoreCxt -> User -> ChatPreviewData 'CTGroup -> ExceptT StoreError IO AChat
+getGroupChatPreview_ db cxt user (GroupChatPD _ groupId lastItemId_ stats) = do
+ groupInfo <- getGroupInfo db cxt user groupId
ts <- liftIO getCurrentTime
lastItem <- case lastItemId_ of
Just lastItemId -> do
@@ -1223,10 +1223,10 @@ getChatContentTypes db User {userId} (ChatRef cType chatId chatScope_) = case cT
("SELECT DISTINCT msg_content_tag FROM chat_items WHERE user_id = ? AND " <> cond <> " AND msg_content_tag IS NOT NULL ORDER BY msg_content_tag")
((userId, chatId) :. params)
-getDirectChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> Maybe MsgContentTag -> ChatPagination -> Maybe Text -> ExceptT StoreError IO (Chat 'CTDirect, Maybe NavigationInfo)
-getDirectChat db vr user contactId contentFilter pagination search_ = do
+getDirectChat :: DB.Connection -> StoreCxt -> User -> Int64 -> Maybe MsgContentTag -> ChatPagination -> Maybe Text -> ExceptT StoreError IO (Chat 'CTDirect, Maybe NavigationInfo)
+getDirectChat db cxt user contactId contentFilter pagination search_ = do
let search = fromMaybe "" search_
- ct <- getContact db vr user contactId
+ ct <- getContact db cxt user contactId
case pagination of
CPLast count -> (,Nothing) <$> getDirectChatLast_ db user ct contentFilter count search
CPAfter afterId count -> (,Nothing) <$> getDirectChatAfter_ db user ct contentFilter afterId count search
@@ -1443,11 +1443,11 @@ getContactNavInfo_ db User {userId} Contact {contactId} afterCI = do
:. (userId, contactId, ciCreatedAt afterCI, cChatItemId afterCI)
)
-getGroupChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> Maybe GroupChatScope -> Maybe MsgContentTag -> ChatPagination -> Maybe Text -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
-getGroupChat db vr user groupId scope_ contentFilter pagination search_ = do
+getGroupChat :: DB.Connection -> StoreCxt -> User -> Int64 -> Maybe GroupChatScope -> Maybe MsgContentTag -> ChatPagination -> Maybe Text -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
+getGroupChat db cxt user groupId scope_ contentFilter pagination search_ = do
let search = fromMaybe "" search_
- g <- getGroupInfo db vr user groupId
- scopeInfo <- mapM (getCreateGroupChatScopeInfo db vr user g) scope_
+ g <- getGroupInfo db cxt user groupId
+ scopeInfo <- mapM (getCreateGroupChatScopeInfo db cxt user g) scope_
case pagination of
CPLast count -> (,Nothing) <$> getGroupChatLast_ db user g scopeInfo contentFilter count search emptyChatStats
CPAfter afterId count -> (,Nothing) <$> getGroupChatAfter_ db user g scopeInfo contentFilter afterId count search
@@ -1457,31 +1457,31 @@ getGroupChat db vr user groupId scope_ contentFilter pagination search_ = do
unless (T.null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search"
getGroupChatInitial_ db user g scopeInfo contentFilter count
-getCreateGroupChatScopeInfo :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScope -> ExceptT StoreError IO GroupChatScopeInfo
-getCreateGroupChatScopeInfo db vr user GroupInfo {membership} = \case
+getCreateGroupChatScopeInfo :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupChatScope -> ExceptT StoreError IO GroupChatScopeInfo
+getCreateGroupChatScopeInfo db cxt user GroupInfo {membership} = \case
GCSMemberSupport Nothing -> do
when (isNothing $ supportChat membership) $ do
ts <- liftIO getCurrentTime
liftIO $ setSupportChatTs db (groupMemberId' membership) ts
pure $ GCSIMemberSupport {groupMember_ = Nothing}
GCSMemberSupport (Just gmId) -> do
- m <- getGroupMemberById db vr user gmId
+ m <- getGroupMemberById db cxt user gmId
when (isNothing $ supportChat m) $ do
ts <- liftIO getCurrentTime
liftIO $ setSupportChatTs db gmId ts
pure GCSIMemberSupport {groupMember_ = Just m}
-getGroupChatScopeInfoForItem :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> ChatItemId -> ExceptT StoreError IO (Maybe GroupChatScopeInfo)
-getGroupChatScopeInfoForItem db vr user g itemId =
- getGroupChatScopeForItem_ db itemId >>= mapM (getGroupChatScopeInfo db vr user g)
+getGroupChatScopeInfoForItem :: DB.Connection -> StoreCxt -> User -> GroupInfo -> ChatItemId -> ExceptT StoreError IO (Maybe GroupChatScopeInfo)
+getGroupChatScopeInfoForItem db cxt user g itemId =
+ getGroupChatScopeForItem_ db itemId >>= mapM (getGroupChatScopeInfo db cxt user g)
-getGroupChatScopeInfo :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScope -> ExceptT StoreError IO GroupChatScopeInfo
-getGroupChatScopeInfo db vr user GroupInfo {membership} = \case
+getGroupChatScopeInfo :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupChatScope -> ExceptT StoreError IO GroupChatScopeInfo
+getGroupChatScopeInfo db cxt user GroupInfo {membership} = \case
GCSMemberSupport Nothing -> case supportChat membership of
Nothing -> throwError $ SEInternalError "no moderators support chat"
Just _supportChat -> pure $ GCSIMemberSupport {groupMember_ = Nothing}
GCSMemberSupport (Just gmId) -> do
- m <- getGroupMemberById db vr user gmId
+ m <- getGroupMemberById db cxt user gmId
case supportChat m of
Nothing -> throwError $ SEInternalError "no support chat"
Just _supportChat -> pure GCSIMemberSupport {groupMember_ = Just m}
@@ -2087,8 +2087,8 @@ updateGroupChatItemsRead db User {userId} GroupInfo {groupId} = do
|]
(CISRcvRead, currentTs, userId, groupId, CISRcvNew)
-updateSupportChatItemsRead :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScopeInfo -> IO (GroupInfo, GroupMember)
-updateSupportChatItemsRead db vr user@User {userId} g@GroupInfo {groupId, membership} scopeInfo = do
+updateSupportChatItemsRead :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupChatScopeInfo -> IO (GroupInfo, GroupMember)
+updateSupportChatItemsRead db cxt user@User {userId} g@GroupInfo {groupId, membership} scopeInfo = do
currentTs <- getCurrentTime
case scopeInfo of
GCSIMemberSupport {groupMember_} -> do
@@ -2126,7 +2126,7 @@ updateSupportChatItemsRead db vr user@User {userId} g@GroupInfo {groupId, member
WHERE group_member_id = ?
|]
(currentTs, groupMemberId)
- m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId
+ m_ <- runExceptT $ getGroupMemberById db cxt user groupMemberId
pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
getGroupUnreadTimedItems :: DB.Connection -> User -> GroupId -> Maybe GroupChatScope -> IO [(ChatItemId, Int)]
@@ -2154,8 +2154,8 @@ getGroupUnreadTimedItems db User {userId} groupId scope =
|]
(userId, groupId, GCSTMemberSupport_, groupMemberId_, CISRcvNew)
-updateGroupChatItemsReadList :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> NonEmpty ChatItemId -> ExceptT StoreError IO ([(ChatItemId, Int)], GroupInfo)
-updateGroupChatItemsReadList db vr user@User {userId} g@GroupInfo {groupId} scopeInfo_ itemIds = do
+updateGroupChatItemsReadList :: DB.Connection -> StoreCxt -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> NonEmpty ChatItemId -> ExceptT StoreError IO ([(ChatItemId, Int)], GroupInfo)
+updateGroupChatItemsReadList db cxt user@User {userId} g@GroupInfo {groupId} scopeInfo_ itemIds = do
currentTs <- liftIO getCurrentTime
-- Possible improvement is to differentiate retrieval queries for each scope,
-- but we rely on UI to not pass item IDs from incorrect scope.
@@ -2164,7 +2164,7 @@ updateGroupChatItemsReadList db vr user@User {userId} g@GroupInfo {groupId} scop
Nothing -> pure g
Just scopeInfo@GCSIMemberSupport {groupMember_} -> do
let decStats = countReadItems groupMember_ readItemsData
- liftIO $ updateGroupScopeUnreadStats db vr user g scopeInfo decStats
+ liftIO $ updateGroupScopeUnreadStats db cxt user g scopeInfo decStats
pure (timedItems readItemsData, g')
where
getUpdateGroupItem :: UTCTime -> ChatItemId -> IO (Maybe (ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt))
@@ -2199,8 +2199,8 @@ updateGroupChatItemsReadList db vr user@User {userId} g@GroupInfo {groupId} scop
addTimedItem acc (itemId, Just ttl, Nothing, _, _) = (itemId, ttl) : acc
addTimedItem acc _ = acc
-updateGroupScopeUnreadStats :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScopeInfo -> (Int, Int, Int) -> IO GroupInfo
-updateGroupScopeUnreadStats db vr user g@GroupInfo {membership} scopeInfo (unread, unanswered, mentions) =
+updateGroupScopeUnreadStats :: DB.Connection -> StoreCxt -> User -> GroupInfo -> GroupChatScopeInfo -> (Int, Int, Int) -> IO GroupInfo
+updateGroupScopeUnreadStats db cxt user g@GroupInfo {membership} scopeInfo (unread, unanswered, mentions) =
case scopeInfo of
GCSIMemberSupport {groupMember_} -> case groupMember_ of
Nothing -> do
@@ -2238,7 +2238,7 @@ updateGroupScopeUnreadStats db vr user g@GroupInfo {membership} scopeInfo (unrea
|]
#endif
(unread, unanswered, mentions, currentTs, groupMemberId)
- m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId
+ m_ <- runExceptT $ getGroupMemberById db cxt user groupMemberId
pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
setGroupChatItemsDeleteAt :: DB.Connection -> User -> GroupId -> [(ChatItemId, Int)] -> UTCTime -> IO [(ChatItemId, UTCTime)]
@@ -2413,8 +2413,8 @@ toGroupChatItem
ciTimed :: Maybe CITimed
ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
-getAllChatItems :: DB.Connection -> VersionRangeChat -> User -> ChatPagination -> Maybe Text -> ExceptT StoreError IO [AChatItem]
-getAllChatItems db vr user@User {userId} pagination search_ = do
+getAllChatItems :: DB.Connection -> StoreCxt -> User -> ChatPagination -> Maybe Text -> ExceptT StoreError IO [AChatItem]
+getAllChatItems db cxt user@User {userId} pagination search_ = do
itemRefs <-
rights . map toChatItemRef <$> case pagination of
CPLast count -> liftIO $ getAllChatItemsLast_ count
@@ -2426,12 +2426,12 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
liftIO getFirstUnreadItemId_ >>= \case
Just itemId -> liftIO . getAllChatItemsAround_ itemId count . aChatItemTs =<< getAChatItem_ itemId
Nothing -> liftIO $ getAllChatItemsLast_ count
- mapM (uncurry (getAChatItem db vr user)) itemRefs
+ mapM (uncurry (getAChatItem db cxt user)) itemRefs
where
search = fromMaybe "" search_
getAChatItem_ itemId = do
chatRef <- getChatRefViaItemId db user itemId
- getAChatItem db vr user chatRef itemId
+ getAChatItem db cxt user chatRef itemId
getAllChatItemsLast_ count =
reverse
<$> DB.query
@@ -3239,8 +3239,8 @@ deleteLocalChatItem db User {userId} NoteFolder {noteFolderId} ci = do
|]
(userId, noteFolderId, itemId)
-getChatItemByFileId :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO AChatItem
-getChatItemByFileId db vr user@User {userId} fileId = do
+getChatItemByFileId :: DB.Connection -> StoreCxt -> User -> Int64 -> ExceptT StoreError IO AChatItem
+getChatItemByFileId db cxt user@User {userId} fileId = do
(chatRef, itemId) <-
ExceptT . firstRow' toChatItemRef (SEChatItemNotFoundByFileId fileId) $
DB.query
@@ -3253,16 +3253,16 @@ getChatItemByFileId db vr user@User {userId} fileId = do
LIMIT 1
|]
(userId, fileId)
- getAChatItem db vr user chatRef itemId
+ getAChatItem db cxt user chatRef itemId
-lookupChatItemByFileId :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO (Maybe AChatItem)
-lookupChatItemByFileId db vr user fileId = do
- fmap Just (getChatItemByFileId db vr user fileId) `catchError` \case
+lookupChatItemByFileId :: DB.Connection -> StoreCxt -> User -> Int64 -> ExceptT StoreError IO (Maybe AChatItem)
+lookupChatItemByFileId db cxt user fileId = do
+ fmap Just (getChatItemByFileId db cxt user fileId) `catchError` \case
SEChatItemNotFoundByFileId {} -> pure Nothing
e -> throwError e
-getChatItemByGroupId :: DB.Connection -> VersionRangeChat -> User -> GroupId -> ExceptT StoreError IO AChatItem
-getChatItemByGroupId db vr user@User {userId} groupId = do
+getChatItemByGroupId :: DB.Connection -> StoreCxt -> User -> GroupId -> ExceptT StoreError IO AChatItem
+getChatItemByGroupId db cxt user@User {userId} groupId = do
(chatRef, itemId) <-
ExceptT . firstRow' toChatItemRef (SEChatItemNotFoundByGroupId groupId) $
DB.query
@@ -3275,7 +3275,7 @@ getChatItemByGroupId db vr user@User {userId} groupId = do
LIMIT 1
|]
(userId, groupId)
- getAChatItem db vr user chatRef itemId
+ getAChatItem db cxt user chatRef itemId
getChatRefViaItemId :: DB.Connection -> User -> ChatItemId -> ExceptT StoreError IO ChatRef
getChatRefViaItemId db User {userId} itemId = do
@@ -3288,17 +3288,17 @@ getChatRefViaItemId db User {userId} itemId = do
(Nothing, Just groupId) -> Right $ ChatRef CTGroup groupId Nothing
(_, _) -> Left $ SEBadChatItem itemId Nothing
-getAChatItem :: DB.Connection -> VersionRangeChat -> User -> ChatRef -> ChatItemId -> ExceptT StoreError IO AChatItem
-getAChatItem db vr user (ChatRef cType chatId scope) itemId = do
+getAChatItem :: DB.Connection -> StoreCxt -> User -> ChatRef -> ChatItemId -> ExceptT StoreError IO AChatItem
+getAChatItem db cxt user (ChatRef cType chatId scope) itemId = do
aci <- case cType of
CTDirect -> do
- ct <- getContact db vr user chatId
+ ct <- getContact db cxt user chatId
(CChatItem msgDir ci) <- getDirectChatItem db user chatId itemId
pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci
CTGroup -> do
- gInfo <- getGroupInfo db vr user chatId
+ gInfo <- getGroupInfo db cxt user chatId
(CChatItem msgDir ci) <- getGroupChatItem db user chatId itemId
- scopeInfo <- mapM (getGroupChatScopeInfo db vr user gInfo) scope
+ scopeInfo <- mapM (getGroupChatScopeInfo db cxt user gInfo) scope
pure $ AChatItem SCTGroup msgDir (GroupChat gInfo scopeInfo) ci
CTLocal -> do
nf <- getNoteFolder db user chatId
@@ -3474,8 +3474,8 @@ setGroupReaction db GroupInfo {groupId} m itemMemberId itemSharedMId sent reacti
|]
(groupId, groupMemberId' m, itemSharedMId, itemMemberId, BI sent, reaction)
-getReactionMembers :: DB.Connection -> VersionRangeChat -> User -> GroupId -> SharedMsgId -> MsgReaction -> IO [MemberReaction]
-getReactionMembers db vr user groupId itemSharedMId reaction = do
+getReactionMembers :: DB.Connection -> StoreCxt -> User -> GroupId -> SharedMsgId -> MsgReaction -> IO [MemberReaction]
+getReactionMembers db cxt user groupId itemSharedMId reaction = do
reactions <-
DB.query
db
@@ -3489,7 +3489,7 @@ getReactionMembers db vr user groupId itemSharedMId reaction = do
where
toMemberReaction :: (GroupMemberId, UTCTime) -> ExceptT StoreError IO MemberReaction
toMemberReaction (groupMemberId, reactionTs) = do
- groupMember <- getGroupMemberById db vr user groupMemberId
+ groupMember <- getGroupMemberById db cxt user groupMemberId
pure MemberReaction {groupMember, reactionTs}
getTimedItems :: DB.Connection -> User -> UTCTime -> IO [((ChatRef, ChatItemId), UTCTime)]
@@ -3587,9 +3587,9 @@ createCIModeration db GroupInfo {groupId} moderatorMember itemMemberId itemShare
|]
(groupId, groupMemberId' moderatorMember, itemMemberId, itemSharedMId, msgId, moderatedAtTs)
-getCIModeration :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> Maybe SharedMsgId -> IO (Maybe CIModeration)
+getCIModeration :: DB.Connection -> StoreCxt -> User -> GroupInfo -> MemberId -> Maybe SharedMsgId -> IO (Maybe CIModeration)
getCIModeration _ _ _ _ _ Nothing = pure Nothing
-getCIModeration db vr user GroupInfo {groupId} itemMemberId (Just sharedMsgId) = do
+getCIModeration db cxt user GroupInfo {groupId} itemMemberId (Just sharedMsgId) = do
r_ <-
maybeFirstRow id $
DB.query
@@ -3603,7 +3603,7 @@ getCIModeration db vr user GroupInfo {groupId} itemMemberId (Just sharedMsgId) =
(groupId, itemMemberId, sharedMsgId)
case r_ of
Just (moderationId, moderatorId, createdByMsgId, moderatedAt) -> do
- runExceptT (getGroupMember db vr user groupId moderatorId) >>= \case
+ runExceptT (getGroupMember db cxt user groupId moderatorId) >>= \case
Right moderatorMember -> pure (Just CIModeration {moderationId, moderatorMember, createdByMsgId, moderatedAt})
_ -> pure Nothing
_ -> pure Nothing
diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs
index 3ba25e700b..749f87b059 100644
--- a/src/Simplex/Chat/Store/Profiles.hs
+++ b/src/Simplex/Chat/Store/Profiles.hs
@@ -394,9 +394,9 @@ createUserContactLink db User {userId} agentConnId (CCLink cReq shortLink) subMo
userContactLinkId <- insertedRowId db
void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff Nothing
-getUserAddressConnection :: DB.Connection -> VersionRangeChat -> User -> ExceptT StoreError IO Connection
-getUserAddressConnection db vr User {userId} = do
- ExceptT . firstRow (toConnection vr) SEUserContactLinkNotFound $
+getUserAddressConnection :: DB.Connection -> StoreCxt -> User -> ExceptT StoreError IO Connection
+getUserAddressConnection db cxt User {userId} = do
+ ExceptT . firstRow (toConnection cxt) SEUserContactLinkNotFound $
DB.query
db
[sql|
@@ -539,8 +539,8 @@ setUserContactLinkShortLink db userContactLinkId shortLink =
|]
(shortLink, BI True, BI True, BI False, userContactLinkId)
-getContactWithoutConnViaAddress :: DB.Connection -> VersionRangeChat -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe Contact)
-getContactWithoutConnViaAddress db vr user@User {userId} (cReqSchema1, cReqSchema2) = do
+getContactWithoutConnViaAddress :: DB.Connection -> StoreCxt -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe Contact)
+getContactWithoutConnViaAddress db cxt user@User {userId} (cReqSchema1, cReqSchema2) = do
ctId_ <-
maybeFirstRow fromOnly $
DB.query
@@ -553,10 +553,10 @@ getContactWithoutConnViaAddress db vr user@User {userId} (cReqSchema1, cReqSchem
WHERE cp.user_id = ? AND cp.contact_link IN (?,?) AND c.connection_id IS NULL
|]
(userId, cReqSchema1, cReqSchema2)
- maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db vr user) ctId_
+ maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db cxt user) ctId_
-getContactWithoutConnViaShortAddress :: DB.Connection -> VersionRangeChat -> User -> ShortLinkContact -> IO (Maybe Contact)
-getContactWithoutConnViaShortAddress db vr user@User {userId} shortLink = do
+getContactWithoutConnViaShortAddress :: DB.Connection -> StoreCxt -> User -> ShortLinkContact -> IO (Maybe Contact)
+getContactWithoutConnViaShortAddress db cxt user@User {userId} shortLink = do
ctId_ <-
maybeFirstRow fromOnly $
DB.query
@@ -569,7 +569,7 @@ getContactWithoutConnViaShortAddress db vr user@User {userId} shortLink = do
WHERE cp.user_id = ? AND cp.contact_link = ? AND c.connection_id IS NULL
|]
(userId, shortLink)
- maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db vr user) ctId_
+ maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db cxt user) ctId_
updateUserAddressSettings :: DB.Connection -> Int64 -> AddressSettings -> IO ()
updateUserAddressSettings db userContactLinkId AddressSettings {businessAddress, autoAccept, autoReply} =
diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs
index ae3f3bc0d2..cbfb80f741 100644
--- a/src/Simplex/Chat/Store/Shared.hs
+++ b/src/Simplex/Chat/Store/Shared.hs
@@ -236,12 +236,12 @@ type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Ma
decodeSimplexName :: Maybe Text -> Maybe SimplexNameInfo
decodeSimplexName = (>>= eitherToMaybe . strDecode . encodeUtf8)
-toConnection :: VersionRangeChat -> ConnectionRow -> Connection
-toConnection vr ((connId, acId, connLevel, viaContact, viaUserContactLink, BI viaGroupLink, groupLinkId, xContactId) :. (customUserProfileId, connStatus, connType, BI contactConnInitiated, localAlias) :. (contactId, groupMemberId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter, quotaErrCounter, chatV, minVer, maxVer) :. Only simplexNameRaw) =
+toConnection :: StoreCxt -> ConnectionRow -> Connection
+toConnection cxt ((connId, acId, connLevel, viaContact, viaUserContactLink, BI viaGroupLink, groupLinkId, xContactId) :. (customUserProfileId, connStatus, connType, BI contactConnInitiated, localAlias) :. (contactId, groupMemberId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter, quotaErrCounter, chatV, minVer, maxVer) :. Only simplexNameRaw) =
Connection
{ connId,
agentConnId = AgentConnId acId,
- connChatVersion = fromMaybe (vr `peerConnChatVersion` peerChatVRange) chatV,
+ connChatVersion = fromMaybe (vr cxt `peerConnChatVersion` peerChatVRange) chatV,
peerChatVRange = peerChatVRange,
connLevel,
viaContact,
@@ -272,9 +272,9 @@ toConnection vr ((connId, acId, connLevel, viaContact, viaUserContactLink, BI vi
entityId_ ConnMember = groupMemberId
entityId_ ConnUserContact = userContactLinkId
-toMaybeConnection :: VersionRangeChat -> MaybeConnectionRow -> Maybe Connection
-toMaybeConnection vr ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, Just viaGroupLink, groupLinkId, xContactId) :. (customUserProfileId, Just connStatus, Just connType, Just contactConnInitiated, Just localAlias) :. (contactId, groupMemberId, userContactLinkId) :. (Just createdAt, code_, verifiedAt_, Just pqSupport, Just pqEncryption, pqSndEnabled_, pqRcvEnabled_, Just authErrCounter, Just quotaErrCounter, connChatVersion, Just minVer, Just maxVer) :. Only simplexNameRaw) =
- Just $ toConnection vr ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, xContactId) :. (customUserProfileId, connStatus, connType, contactConnInitiated, localAlias) :. (contactId, groupMemberId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled_, pqRcvEnabled_, authErrCounter, quotaErrCounter, connChatVersion, minVer, maxVer) :. Only simplexNameRaw)
+toMaybeConnection :: StoreCxt -> MaybeConnectionRow -> Maybe Connection
+toMaybeConnection cxt ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, Just viaGroupLink, groupLinkId, xContactId) :. (customUserProfileId, Just connStatus, Just connType, Just contactConnInitiated, Just localAlias) :. (contactId, groupMemberId, userContactLinkId) :. (Just createdAt, code_, verifiedAt_, Just pqSupport, Just pqEncryption, pqSndEnabled_, pqRcvEnabled_, Just authErrCounter, Just quotaErrCounter, connChatVersion, Just minVer, Just maxVer) :. Only simplexNameRaw) =
+ Just $ toConnection cxt ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, xContactId) :. (customUserProfileId, connStatus, connType, contactConnInitiated, localAlias) :. (contactId, groupMemberId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled_, pqRcvEnabled_, authErrCounter, quotaErrCounter, connChatVersion, minVer, maxVer) :. Only simplexNameRaw)
toMaybeConnection _ _ = Nothing
-- | Creates a new connection row. The @simplexName@ argument is a TRANSIENT
@@ -555,11 +555,11 @@ type ContactRow = Only ContactId :. ContactRow'
-- ct.simplex_name -> Contact.simplexName (user's locally-known label)
-- cp.simplex_name -> LocalProfile.simplexName (peer's broadcast claim)
-toContact :: VersionRangeChat -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact
-toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL, ctSimplexNameRaw, cpSimplexNameRaw, simplexNameVerifiedAt)) :. connRow) =
+toContact :: StoreCxt -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact
+toContact cxt user chatTags ((Only contactId :. (profileId, localDisplayName, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent) :. groupDirectInvRow :. (uiThemes, BI chatDeleted, customData, chatItemTTL, ctSimplexNameRaw, cpSimplexNameRaw, simplexNameVerifiedAt)) :. connRow) =
let simplexName = decodeSimplexName ctSimplexNameRaw
profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, simplexName = decodeSimplexName cpSimplexNameRaw, peerType, preferences, localAlias}
- activeConn = toMaybeConnection vr connRow
+ activeConn = toMaybeConnection cxt connRow
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
incognito = maybe False connIncognito activeConn
mergedPreferences = contactUserPreferences user userPreferences preferences incognito
@@ -741,9 +741,9 @@ type GroupMemberRow = (GroupMemberId, GroupId, Int64, MemberId, VersionChat, Ver
type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe ChatPeerType, LocalAlias, Maybe Preferences, Maybe Text)
-toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo
-toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, publicMemberCount, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri) :. groupKeysRow :. (gSimplexNameRaw, gpSimplexNameRaw, simplexNameVerifiedAt) :. userMemberRow) =
- let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
+toGroupInfo :: StoreCxt -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo
+toGroupInfo cxt userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (BI useRelays, relayOwnStatus, uiThemes, currentMembers, publicMemberCount, customData, chatItemTTL, membersRequireAttention, viaGroupLinkUri) :. groupKeysRow :. (gSimplexNameRaw, gpSimplexNameRaw, simplexNameVerifiedAt) :. userMemberRow) =
+ let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr cxt}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
fullGroupPreferences = mergeGroupPreferences groupPreferences
publicGroup = toPublicGroupProfile groupType_ groupLink_ publicGroupId_ (toPublicGroupAccess accessRow)
@@ -829,9 +829,9 @@ groupMemberQuery =
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
|]
-toContactMember :: VersionRangeChat -> User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember
-toContactMember vr User {userContactId} (memberRow :. connRow) =
- (toGroupMember userContactId memberRow) {activeConn = toMaybeConnection vr connRow}
+toContactMember :: StoreCxt -> User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember
+toContactMember cxt User {userContactId} (memberRow :. connRow) =
+ (toGroupMember userContactId memberRow) {activeConn = toMaybeConnection cxt connRow}
rowToLocalProfile :: ProfileRow -> LocalProfile
rowToLocalProfile (profileId, displayName, fullName, shortDescr, image, contactLink, peerType, localAlias, preferences, simplexNameRaw) =
@@ -950,10 +950,10 @@ addGroupChatTags db g@GroupInfo {groupId} = do
chatTags <- getGroupChatTags db groupId
pure (g :: GroupInfo) {chatTags}
-getGroupInfo :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO GroupInfo
-getGroupInfo db vr User {userId, userContactId} groupId = ExceptT $ do
+getGroupInfo :: DB.Connection -> StoreCxt -> User -> Int64 -> ExceptT StoreError IO GroupInfo
+getGroupInfo db cxt User {userId, userContactId} groupId = ExceptT $ do
chatTags <- getGroupChatTags db groupId
- firstRow (toGroupInfo vr userContactId chatTags) (SEGroupNotFound groupId) $
+ firstRow (toGroupInfo cxt userContactId chatTags) (SEGroupNotFound groupId) $
DB.query
db
(groupInfoQuery <> " WHERE g.group_id = ? AND g.user_id = ? AND mu.contact_id = ?")
diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs
index d5c2106876..a1f470a872 100644
--- a/src/Simplex/Chat/Types.hs
+++ b/src/Simplex/Chat/Types.hs
@@ -2049,6 +2049,10 @@ type VersionChat = Version ChatVersion
type VersionRangeChat = VersionRange ChatVersion
+-- | Store-wide context passed to store functions in place of the bare `vr`
+-- parameter. Built from config by mkStoreCxt; more fields are added here over time.
+newtype StoreCxt = StoreCxt {vr :: VersionRangeChat}
+
pattern VersionChat :: Word16 -> VersionChat
pattern VersionChat v = Version v
diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs
index 6025ad859e..7f03c5c2c0 100644
--- a/tests/ChatTests/Utils.hs
+++ b/tests/ChatTests/Utils.hs
@@ -23,7 +23,7 @@ import Data.List (isPrefixOf, isSuffixOf)
import Data.Maybe (fromMaybe)
import Data.String
import qualified Data.Text as T
-import Simplex.Chat.Controller (ChatConfig (..), ChatController (..))
+import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), mkStoreCxt)
import Simplex.Chat.Markdown (viewName)
import Simplex.Chat.Messages.CIContent (e2eInfoNoPQText, e2eInfoPQText)
import Simplex.Chat.Protocol
@@ -702,10 +702,10 @@ getCtConn cc contactId = getTestCCContact cc contactId >>= maybe (fail "no conne
getTestCCContact :: TestCC -> ContactId -> IO Contact
getTestCCContact cc contactId = do
- let TestCC {chatController = ChatController {config = ChatConfig {chatVRange = vr}}} = cc
+ let TestCC {chatController = ChatController {config}} = cc
withCCTransaction cc $ \db ->
withCCUser cc $ \user ->
- runExceptT (getContact db vr user contactId) >>= either (fail . show) pure
+ runExceptT (getContact db (mkStoreCxt config) user contactId) >>= either (fail . show) pure
lastItemId :: HasCallStack => TestCC -> IO String
lastItemId cc = do