mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-25 20:44:38 +00:00
core, ios: create contact requests with attached contact (#5967)
This commit is contained in:
@@ -766,7 +766,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
|
||||
case userContactLinkCreated(user: User, connLinkContact: CreatedConnLink)
|
||||
case userContactLinkDeleted(user: User)
|
||||
case acceptingContactRequest(user: UserRef, contact: Contact)
|
||||
case contactRequestRejected(user: UserRef)
|
||||
case contactRequestRejected(user: UserRef, contactRequest: UserContactRequest, contact_: Contact?)
|
||||
case networkStatuses(user_: UserRef?, networkStatuses: [ConnNetworkStatus])
|
||||
case newChatItems(user: UserRef, chatItems: [AChatItem])
|
||||
case groupChatItemsDeleted(user: UserRef, groupInfo: GroupInfo, chatItemIDs: Set<Int64>, byUser: Bool, member_: GroupMember?)
|
||||
@@ -842,7 +842,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
|
||||
case let .userContactLinkCreated(u, connLink): return withUser(u, String(describing: connLink))
|
||||
case .userContactLinkDeleted: return noDetails
|
||||
case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact))
|
||||
case .contactRequestRejected: return noDetails
|
||||
case let .contactRequestRejected(u, contactRequest, contact_): return withUser(u, "contactRequest: \(String(describing: contactRequest))\ncontact_: \(String(describing: contact_))")
|
||||
case let .networkStatuses(u, statuses): return withUser(u, String(describing: statuses))
|
||||
case let .newChatItems(u, chatItems):
|
||||
let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n")
|
||||
@@ -1028,7 +1028,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
|
||||
case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?)
|
||||
case contactConnecting(user: UserRef, contact: Contact)
|
||||
case contactSndReady(user: UserRef, contact: Contact)
|
||||
case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest)
|
||||
case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest, contact_: Contact?)
|
||||
case contactUpdated(user: UserRef, toContact: Contact)
|
||||
case groupMemberUpdated(user: UserRef, groupInfo: GroupInfo, fromMember: GroupMember, toMember: GroupMember)
|
||||
case contactsMerged(user: UserRef, intoContact: Contact, mergedContact: Contact)
|
||||
@@ -1179,7 +1179,7 @@ enum ChatEvent: Decodable, ChatAPIResult {
|
||||
case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact))
|
||||
case let .contactConnecting(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .contactSndReady(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest))
|
||||
case let .receivedContactRequest(u, contactRequest, contact_): return withUser(u, "contactRequest: \(String(describing: contactRequest))\ncontact_: \(String(describing: contact_))")
|
||||
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
|
||||
case let .groupMemberUpdated(u, groupInfo, fromMember, toMember): return withUser(u, "groupInfo: \(groupInfo)\nfromMember: \(fromMember)\ntoMember: \(toMember)")
|
||||
case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)")
|
||||
|
||||
@@ -1216,6 +1216,7 @@ struct ShowingInvitation {
|
||||
var connChatUsed: Bool
|
||||
}
|
||||
|
||||
// TODO [short links] incognito if !userAddress.shortLinkDataSet; or remove if no access to chat model here
|
||||
struct NTFContactRequest {
|
||||
var incognito: Bool
|
||||
var chatId: String
|
||||
|
||||
@@ -63,7 +63,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
let chatId = content.userInfo["chatId"] as? String {
|
||||
let incognito = action == ntfActionAcceptContactIncognito
|
||||
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
|
||||
Task { await acceptContactRequest(incognito: incognito, contactRequest: contactRequest) }
|
||||
Task { await acceptContactRequest(incognito: incognito, contactRequestId: contactRequest.apiId) }
|
||||
} else {
|
||||
chatModel.ntfContactRequest = NTFContactRequest(incognito: incognito, chatId: chatId)
|
||||
}
|
||||
@@ -161,7 +161,9 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
identifier: ntfActionAcceptContact,
|
||||
title: NSLocalizedString("Accept", comment: "accept contact request via notification"),
|
||||
options: .foreground
|
||||
), UNNotificationAction(
|
||||
),
|
||||
// TODO [short links] if !userAddress.shortLinkDataSet; or remove if no access to chat model here
|
||||
UNNotificationAction(
|
||||
identifier: ntfActionAcceptContactIncognito,
|
||||
title: NSLocalizedString("Accept incognito", comment: "accept contact request via notification"),
|
||||
options: .foreground
|
||||
|
||||
@@ -1275,9 +1275,9 @@ func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Cont
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiRejectContactRequest(contactReqId: Int64) async throws {
|
||||
func apiRejectContactRequest(contactReqId: Int64) async throws -> Contact? {
|
||||
let r: ChatResponse1 = try await chatSendCmd(.apiRejectContact(contactReqId: contactReqId))
|
||||
if case .contactRequestRejected = r { return }
|
||||
if case let .contactRequestRejected(_, _, contact_) = r { return contact_ }
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
@@ -1511,11 +1511,15 @@ func networkErrorAlert<R>(_ res: APIResult<R>) -> Alert? {
|
||||
}
|
||||
}
|
||||
|
||||
func acceptContactRequest(incognito: Bool, contactRequest: UserContactRequest) async {
|
||||
if let contact = await apiAcceptContactRequest(incognito: incognito, contactReqId: contactRequest.apiId) {
|
||||
func acceptContactRequest(incognito: Bool, contactRequestId: Int64) async {
|
||||
if let contact = await apiAcceptContactRequest(incognito: incognito, contactReqId: contactRequestId) {
|
||||
let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: [])
|
||||
await MainActor.run {
|
||||
ChatModel.shared.replaceChat(contactRequest.id, chat)
|
||||
if contact.contactRequestId != nil { // means contact request was initially created with contact, so we don't need to replace it
|
||||
ChatModel.shared.updateContact(contact)
|
||||
} else {
|
||||
ChatModel.shared.replaceChat(contactRequestChatId(contactRequestId), chat)
|
||||
}
|
||||
NetworkModel.shared.setContactNetworkStatus(contact, .connected)
|
||||
}
|
||||
if contact.sndReady {
|
||||
@@ -1528,12 +1532,27 @@ func acceptContactRequest(incognito: Bool, contactRequest: UserContactRequest) a
|
||||
}
|
||||
}
|
||||
|
||||
func rejectContactRequest(_ contactRequest: UserContactRequest) async {
|
||||
func rejectContactRequest(_ contactRequestId: Int64, dismissToChatList: Bool = false) async {
|
||||
do {
|
||||
try await apiRejectContactRequest(contactReqId: contactRequest.apiId)
|
||||
DispatchQueue.main.async { ChatModel.shared.removeChat(contactRequest.id) }
|
||||
let contact_ = try await apiRejectContactRequest(contactReqId: contactRequestId)
|
||||
await MainActor.run {
|
||||
if let contact = contact_ { // means contact request was initially created with contact, so we need to remove contact chat
|
||||
ChatModel.shared.removeChat(contact.id)
|
||||
} else {
|
||||
ChatModel.shared.removeChat(contactRequestChatId(contactRequestId))
|
||||
}
|
||||
if dismissToChatList {
|
||||
ChatModel.shared.chatId = nil
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("rejectContactRequest: \(responseError(error))")
|
||||
await MainActor.run {
|
||||
showAlert(
|
||||
NSLocalizedString("Error rejecting contact request", comment: "alert title"),
|
||||
message: responseError(error)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2106,17 +2125,28 @@ func processReceivedMsg(_ res: ChatEvent) async {
|
||||
await MainActor.run {
|
||||
n.setContactNetworkStatus(contact, .connected)
|
||||
}
|
||||
case let .receivedContactRequest(user, contactRequest):
|
||||
case let .receivedContactRequest(user, contactRequest, contact_):
|
||||
if active(user) {
|
||||
let cInfo = ChatInfo.contactRequest(contactRequest: contactRequest)
|
||||
await MainActor.run {
|
||||
if m.hasChat(contactRequest.id) {
|
||||
m.updateChatInfo(cInfo)
|
||||
if let contact = contact_ { // means contact request was created with contact, so we need to add/update contact chat
|
||||
if m.hasChat(contact.id) {
|
||||
m.updateContact(contact)
|
||||
} else {
|
||||
m.addChat(Chat(
|
||||
chatInfo: ChatInfo.direct(contact: contact),
|
||||
chatItems: []
|
||||
))
|
||||
}
|
||||
} else {
|
||||
m.addChat(Chat(
|
||||
chatInfo: cInfo,
|
||||
chatItems: []
|
||||
))
|
||||
let cInfo = ChatInfo.contactRequest(contactRequest: contactRequest)
|
||||
if m.hasChat(contactRequest.id) {
|
||||
m.updateChatInfo(cInfo)
|
||||
} else {
|
||||
m.addChat(Chat(
|
||||
chatInfo: cInfo,
|
||||
chatItems: []
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ struct SimpleXApp: App {
|
||||
if let ncr = chatModel.ntfContactRequest {
|
||||
await MainActor.run { chatModel.ntfContactRequest = nil }
|
||||
if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo {
|
||||
Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) }
|
||||
Task { await acceptContactRequest(incognito: ncr.incognito, contactRequestId: contactRequest.apiId) }
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
|
||||
@@ -119,7 +119,8 @@ struct ChatView: View {
|
||||
let reason = chat.chatInfo.userCantSendReason
|
||||
let composeEnabled = (
|
||||
chat.chatInfo.sendMsgEnabled ||
|
||||
(chat.chatInfo.groupInfo?.nextConnectPrepared ?? false) // allow to join prepared group without message
|
||||
(chat.chatInfo.groupInfo?.nextConnectPrepared ?? false) || // allow to join prepared group without message
|
||||
(chat.chatInfo.contact?.nextAcceptContactRequest ?? false) // allow to accept or reject contact request
|
||||
)
|
||||
ComposeView(
|
||||
chat: chat,
|
||||
@@ -764,7 +765,8 @@ struct ChatView: View {
|
||||
if case let .direct(contact) = chat.chatInfo,
|
||||
!contact.sndReady,
|
||||
contact.active,
|
||||
!contact.sendMsgToConnect {
|
||||
!contact.sendMsgToConnect,
|
||||
!contact.nextAcceptContactRequest {
|
||||
Text("connecting…")
|
||||
.font(.caption)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
|
||||
@@ -350,6 +350,12 @@ struct ComposeView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Divider()
|
||||
if let contact = chat.chatInfo.contact,
|
||||
contact.nextAcceptContactRequest,
|
||||
let contactRequestId = contact.contactRequestId {
|
||||
ContextContactRequestActionsView(contactRequestId: contactRequestId)
|
||||
}
|
||||
|
||||
if let groupInfo = chat.chatInfo.groupInfo,
|
||||
case let .groupChatScopeContext(groupScopeInfo) = im.secondaryIMFilter,
|
||||
case let .memberSupport(member) = groupScopeInfo,
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// ContextContactRequestActionsView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by spaced4ndy on 02.05.2025.
|
||||
// Copyright © 2025 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct ContextContactRequestActionsView: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var contactRequestId: Int64
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
ZStack {
|
||||
Text("Reject")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
showRejectRequestAlert(contactRequestId)
|
||||
}
|
||||
|
||||
ZStack {
|
||||
Text("Accept")
|
||||
.foregroundColor(theme.colors.primary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
showAcceptRequestAlert(contactRequestId)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 54)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(.thinMaterial)
|
||||
}
|
||||
}
|
||||
|
||||
func showRejectRequestAlert(_ contactRequestId: Int64) {
|
||||
showAlert(
|
||||
title: NSLocalizedString("Reject contact request", comment: "alert title"),
|
||||
message: NSLocalizedString("The sender will NOT be notified", comment: "alert message"),
|
||||
buttonTitle: "Reject",
|
||||
buttonAction: {
|
||||
Task {
|
||||
await rejectContactRequest(contactRequestId, dismissToChatList: true)
|
||||
}
|
||||
},
|
||||
cancelButton: true
|
||||
)
|
||||
}
|
||||
|
||||
func showAcceptRequestAlert(_ contactRequestId: Int64) {
|
||||
showAlert(
|
||||
NSLocalizedString("Accept contact request", comment: "alert title"),
|
||||
actions: {[
|
||||
UIAlertAction(
|
||||
title: NSLocalizedString("Accept", comment: "alert action"),
|
||||
style: .default,
|
||||
handler: { _ in
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) }
|
||||
}
|
||||
),
|
||||
// TODO [short links] if !userAddress.shortLinkDataSet; check other places
|
||||
// UIAlertAction(
|
||||
// title: NSLocalizedString("Accept incognito", comment: "alert action"),
|
||||
// style: .default,
|
||||
// handler: { _ in
|
||||
// Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
|
||||
// }
|
||||
// ),
|
||||
UIAlertAction(
|
||||
title: NSLocalizedString("Cancel", comment: "alert action"),
|
||||
style: .default
|
||||
)
|
||||
]}
|
||||
)
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContextContactRequestActionsView(
|
||||
contactRequestId: 1
|
||||
)
|
||||
}
|
||||
@@ -122,29 +122,51 @@ struct ChatListNavLink: View {
|
||||
label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }
|
||||
)
|
||||
.frameCompat(height: dynamicRowHeight)
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
markReadButton()
|
||||
toggleFavoriteButton()
|
||||
toggleNtfsButton(chat: chat)
|
||||
.if(!contact.nextAcceptContactRequest) { v in
|
||||
v.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
markReadButton()
|
||||
toggleFavoriteButton()
|
||||
toggleNtfsButton(chat: chat)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
tagChatButton(chat)
|
||||
if !chat.chatItems.isEmpty {
|
||||
clearChatButton()
|
||||
if contact.nextAcceptContactRequest,
|
||||
let contactRequestId = contact.contactRequestId {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) }
|
||||
} label: { SwipeLabel(NSLocalizedString("Accept", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI) }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) }
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequestId))
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Reject", comment: "swipe action"), systemImage: "multiply.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.red)
|
||||
} else {
|
||||
tagChatButton(chat)
|
||||
if !chat.chatItems.isEmpty {
|
||||
clearChatButton()
|
||||
}
|
||||
Button {
|
||||
deleteContactDialog(
|
||||
chat,
|
||||
contact,
|
||||
dismissToChatList: false,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { actionSheet = $0 },
|
||||
showSheetContent: { sheet = $0 }
|
||||
)
|
||||
} label: {
|
||||
deleteLabel
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
Button {
|
||||
deleteContactDialog(
|
||||
chat,
|
||||
contact,
|
||||
dismissToChatList: false,
|
||||
showAlert: { alert = $0 },
|
||||
showActionSheet: { actionSheet = $0 },
|
||||
showSheetContent: { sheet = $0 }
|
||||
)
|
||||
} label: {
|
||||
deleteLabel
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,17 +458,17 @@ struct ChatListNavLink: View {
|
||||
.frameCompat(height: dynamicRowHeight)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) }
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) }
|
||||
} label: { SwipeLabel(NSLocalizedString("Accept", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI) }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) }
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Accept incognito", comment: "swipe action"), systemImage: "theatermasks.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest))
|
||||
AlertManager.shared.showAlert(rejectContactRequestAlert(contactRequest.apiId))
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Reject", comment: "swipe action"), systemImage: "multiply.fill", inverted: oneHandUI)
|
||||
}
|
||||
@@ -455,9 +477,9 @@ struct ChatListNavLink: View {
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { showContactRequestDialog = true }
|
||||
.confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
|
||||
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } }
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } }
|
||||
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest) } }
|
||||
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } }
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } }
|
||||
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest.apiId) } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -619,12 +641,12 @@ extension View {
|
||||
}
|
||||
}
|
||||
|
||||
func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert {
|
||||
func rejectContactRequestAlert(_ contactRequestId: Int64) -> Alert {
|
||||
Alert(
|
||||
title: Text("Reject contact request"),
|
||||
message: Text("The sender will NOT be notified"),
|
||||
primaryButton: .destructive(Text("Reject")) {
|
||||
Task { await rejectContactRequest(contactRequest) }
|
||||
Task { await rejectContactRequest(contactRequestId) }
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
|
||||
@@ -164,7 +164,16 @@ struct ChatPreviewView: View {
|
||||
let t = Text(chat.chatInfo.chatViewName).font(.title3).fontWeight(.bold)
|
||||
switch chat.chatInfo {
|
||||
case let .direct(contact):
|
||||
previewTitle(contact.verified == true ? verifiedIcon + t : t).foregroundColor(deleting ? Color.secondary : nil)
|
||||
let color = (
|
||||
deleting
|
||||
? Color.secondary
|
||||
: (
|
||||
contact.nextAcceptContactRequest
|
||||
? theme.colors.primary
|
||||
: nil
|
||||
)
|
||||
)
|
||||
previewTitle(contact.verified == true ? verifiedIcon + t : t).foregroundColor(color)
|
||||
case let .group(groupInfo, _):
|
||||
let v = previewTitle(t)
|
||||
switch (groupInfo.membership.memberStatus) {
|
||||
|
||||
@@ -28,6 +28,8 @@ struct ContactListNavLink: View {
|
||||
switch contactType {
|
||||
case .recent:
|
||||
recentContactNavLink(contact)
|
||||
case .contactWithRequest:
|
||||
contactWithRequestNavLink(contact)
|
||||
case .chatDeleted:
|
||||
deletedChatNavLink(contact)
|
||||
case .card:
|
||||
@@ -78,6 +80,36 @@ struct ContactListNavLink: View {
|
||||
}
|
||||
}
|
||||
|
||||
func contactWithRequestNavLink(_ contact: Contact) -> some View {
|
||||
Button {
|
||||
dismissAllSheets(animated: true) {
|
||||
ItemsModel.shared.loadOpenChat(contact.id)
|
||||
}
|
||||
} label: {
|
||||
contactRequestPreview()
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if let contactRequestId = contact.contactRequestId {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) }
|
||||
} label: { Label("Accept", systemImage: "checkmark") }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
alert = SomeAlert(alert: rejectContactRequestAlert(contactRequestId), id: "rejectContactRequestAlert")
|
||||
} label: {
|
||||
Label("Reject", systemImage: "multiply")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deletedChatNavLink(_ contact: Contact) -> some View {
|
||||
Button {
|
||||
Task {
|
||||
@@ -219,36 +251,36 @@ struct ContactListNavLink: View {
|
||||
Button {
|
||||
showContactRequestDialog = true
|
||||
} label: {
|
||||
contactRequestPreview(contactRequest)
|
||||
contactRequestPreview()
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) }
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) }
|
||||
} label: { Label("Accept", systemImage: "checkmark") }
|
||||
.tint(theme.colors.primary)
|
||||
Button {
|
||||
Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) }
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) }
|
||||
} label: {
|
||||
Label("Accept incognito", systemImage: "theatermasks")
|
||||
}
|
||||
.tint(.indigo)
|
||||
Button {
|
||||
alert = SomeAlert(alert: rejectContactRequestAlert(contactRequest), id: "rejectContactRequestAlert")
|
||||
alert = SomeAlert(alert: rejectContactRequestAlert(contactRequest.apiId), id: "rejectContactRequestAlert")
|
||||
} label: {
|
||||
Label("Reject", systemImage: "multiply")
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
.confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) {
|
||||
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } }
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequest: contactRequest) } }
|
||||
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest) } }
|
||||
Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequest.apiId) } }
|
||||
Button("Accept incognito") { Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequest.apiId) } }
|
||||
Button("Reject (sender NOT notified)", role: .destructive) { Task { await rejectContactRequest(contactRequest.apiId) } }
|
||||
}
|
||||
}
|
||||
|
||||
func contactRequestPreview(_ contactRequest: UserContactRequest) -> some View {
|
||||
func contactRequestPreview() -> some View {
|
||||
HStack{
|
||||
ProfileImage(imageStr: contactRequest.image, size: 30)
|
||||
ProfileImage(imageStr: chat.chatInfo.image, size: 30)
|
||||
|
||||
Text(chat.chatInfo.chatViewName)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
enum ContactType: Int {
|
||||
case card, request, recent, chatDeleted, unlisted
|
||||
case card, contactWithRequest, request, recent, chatDeleted, unlisted
|
||||
}
|
||||
|
||||
struct NewChatMenuButton: View {
|
||||
@@ -42,7 +42,7 @@ private var indent: CGFloat = 36
|
||||
|
||||
struct NewChatSheet: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@State private var baseContactTypes: [ContactType] = [.card, .request, .recent]
|
||||
@State private var baseContactTypes: [ContactType] = [.card, .contactWithRequest, .request, .recent]
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@State private var searchMode = false
|
||||
@FocusState var searchFocussed: Bool
|
||||
@@ -191,7 +191,9 @@ func chatContactType(_ chat: Chat) -> ContactType {
|
||||
case .contactRequest:
|
||||
return .request
|
||||
case let .direct(contact):
|
||||
if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active {
|
||||
if contact.nextAcceptContactRequest {
|
||||
return .contactWithRequest
|
||||
} else if contact.activeConn == nil && contact.profile.contactLink != nil && contact.active {
|
||||
return .card
|
||||
} else if contact.chatDeleted {
|
||||
return .chatDeleted
|
||||
|
||||
@@ -184,6 +184,7 @@
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
64D0C2C629FAC1EC00B38D5F /* AddContactLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */; };
|
||||
64E5E3632DF71A4E00A4D530 /* ContextContactRequestActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E5E3622DF71A4E00A4D530 /* ContextContactRequestActionsView.swift */; };
|
||||
64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; };
|
||||
64EEB0F72C353F1C00972D62 /* ServersSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EEB0F62C353F1C00972D62 /* ServersSummaryView.swift */; };
|
||||
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; };
|
||||
@@ -548,6 +549,7 @@
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactLearnMore.swift; sourceTree = "<group>"; };
|
||||
64DAE1502809D9F5000DA960 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
|
||||
64E5E3622DF71A4E00A4D530 /* ContextContactRequestActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextContactRequestActionsView.swift; sourceTree = "<group>"; };
|
||||
64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = "<group>"; };
|
||||
64EEB0F62C353F1C00972D62 /* ServersSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersSummaryView.swift; sourceTree = "<group>"; };
|
||||
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = "<group>"; };
|
||||
@@ -1084,6 +1086,7 @@
|
||||
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */,
|
||||
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */,
|
||||
64A77A012DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift */,
|
||||
64E5E3622DF71A4E00A4D530 /* ContextContactRequestActionsView.swift */,
|
||||
);
|
||||
path = ComposeMessage;
|
||||
sourceTree = "<group>";
|
||||
@@ -1576,6 +1579,7 @@
|
||||
5C5E5D3B2824468B00B0488A /* ActiveCallView.swift in Sources */,
|
||||
B70A39732D24090D00E80A5F /* TagListView.swift in Sources */,
|
||||
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */,
|
||||
64E5E3632DF71A4E00A4D530 /* ContextContactRequestActionsView.swift in Sources */,
|
||||
6440CA00288857A10062C672 /* CIEventView.swift in Sources */,
|
||||
5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */,
|
||||
5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */,
|
||||
|
||||
@@ -1719,6 +1719,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||
var updatedAt: Date
|
||||
var chatTs: Date?
|
||||
public var connLinkToConnect: CreatedConnLink?
|
||||
public var contactRequestId: Int64?
|
||||
var contactGroupMemberId: Int64?
|
||||
var contactGrpInvSent: Bool
|
||||
public var chatTags: [Int64]
|
||||
@@ -1733,6 +1734,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||
public var active: Bool { get { contactStatus == .active } }
|
||||
public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } }
|
||||
public var nextConnectPrepared: Bool { get { connLinkToConnect != nil && activeConn == nil } }
|
||||
public var nextAcceptContactRequest: Bool { get { contactRequestId != nil && activeConn == nil } }
|
||||
public var sendMsgToConnect: Bool { get { nextSendGrpInv || nextConnectPrepared } }
|
||||
public var displayName: String { localAlias == "" ? profile.displayName : localAlias }
|
||||
public var fullName: String { get { profile.fullName } }
|
||||
@@ -1908,7 +1910,7 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable {
|
||||
var createdAt: Date
|
||||
public var updatedAt: Date
|
||||
|
||||
public var id: ChatId { get { "<@\(contactRequestId)" } }
|
||||
public var id: ChatId { get { contactRequestChatId(contactRequestId) } }
|
||||
public var apiId: Int64 { get { contactRequestId } }
|
||||
var ready: Bool { get { true } }
|
||||
public var displayName: String { get { profile.displayName } }
|
||||
@@ -1927,6 +1929,10 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable {
|
||||
)
|
||||
}
|
||||
|
||||
public func contactRequestChatId(_ contactRequestId: Int64) -> ChatId {
|
||||
return "<@\(contactRequestId)"
|
||||
}
|
||||
|
||||
public struct PendingContactConnection: Decodable, NamedChat, Hashable {
|
||||
public var pccConnId: Int64
|
||||
var pccAgentConnId: String
|
||||
|
||||
@@ -668,7 +668,7 @@ data ChatResponse
|
||||
| CRContactsList {user :: User, contacts :: [Contact]}
|
||||
| CRUserContactLink {user :: User, contactLink :: UserContactLink}
|
||||
| CRUserContactLinkUpdated {user :: User, contactLink :: UserContactLink}
|
||||
| CRContactRequestRejected {user :: User, contactRequest :: UserContactRequest}
|
||||
| CRContactRequestRejected {user :: User, contactRequest :: UserContactRequest, contact_ :: Maybe Contact}
|
||||
| CRUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact}
|
||||
| CRUserDeletedMembers {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], withMessages :: Bool}
|
||||
| CRGroupsList {user :: User, groups :: [(GroupInfo, GroupSummary)]}
|
||||
@@ -781,7 +781,7 @@ data ChatEvent
|
||||
| CEvtGroupMemberUpdated {user :: User, groupInfo :: GroupInfo, fromMember :: GroupMember, toMember :: GroupMember}
|
||||
| CEvtContactsMerged {user :: User, intoContact :: Contact, mergedContact :: Contact, updatedContact :: Contact}
|
||||
| CEvtContactDeletedByContact {user :: User, contact :: Contact}
|
||||
| CEvtReceivedContactRequest {user :: User, contactRequest :: UserContactRequest}
|
||||
| CEvtReceivedContactRequest {user :: User, contactRequest :: UserContactRequest, contact_ :: Maybe Contact}
|
||||
| CEvtAcceptingContactRequest {user :: User, contact :: Contact} -- there is the same command response
|
||||
| CEvtAcceptingBusinessRequest {user :: User, groupInfo :: GroupInfo}
|
||||
| CEvtContactRequestAlreadyAccepted {user :: User, contact :: Contact}
|
||||
|
||||
@@ -1144,6 +1144,12 @@ processChatCommand' vr = \case
|
||||
withFastStore' $ \db -> deleteNoteFolderCIs db user nf
|
||||
pure $ CRChatCleared user (AChatInfo SCTLocal $ LocalChat nf)
|
||||
_ -> throwCmdError "not supported"
|
||||
-- TODO [short links] prohibit incognito if short link data is set for address
|
||||
-- TODO - add user_contact_links.short_link_data_set; UserContactLink.shortLinkDataSet
|
||||
-- TODO - same for auto-accept:
|
||||
-- TODO - set incognito to false on setting short link data
|
||||
-- TODO - ignore on accept (for foolproofing)
|
||||
-- TODO - hide in UI
|
||||
APIAcceptContact incognito connReqId -> withUser $ \_ -> do
|
||||
userContactLinkId <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId
|
||||
withUserContactLock "acceptContact" userContactLinkId $ do
|
||||
@@ -1163,12 +1169,17 @@ processChatCommand' vr = \case
|
||||
APIRejectContact connReqId -> withUser $ \user -> do
|
||||
userContactLinkId <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId
|
||||
withUserContactLock "rejectContact" userContactLinkId $ do
|
||||
cReq@UserContactRequest {agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId} <-
|
||||
withFastStore $ \db ->
|
||||
getContactRequest db user connReqId
|
||||
`storeFinally` liftIO (deleteContactRequest db user connReqId)
|
||||
(cReq@UserContactRequest {agentContactConnId = AgentConnId connId, agentInvitationId = AgentInvId invId}, ct_) <-
|
||||
withFastStore $ \db -> do
|
||||
cReq@UserContactRequest {contactId_} <- getContactRequest db user connReqId
|
||||
ct_ <- forM contactId_ $ \contactId -> do
|
||||
ct <- getContact db vr user contactId
|
||||
deleteContact db user ct
|
||||
pure ct
|
||||
liftIO $ deleteContactRequest db user connReqId
|
||||
pure (cReq, ct_)
|
||||
withAgent $ \a -> rejectContact a connId invId
|
||||
pure $ CRContactRequestRejected user cReq
|
||||
pure $ CRContactRequestRejected user cReq ct_
|
||||
APISendCallInvitation contactId callType -> withUser $ \user -> do
|
||||
-- party initiating call
|
||||
ct <- withFastStore $ \db -> getContact db vr user contactId
|
||||
|
||||
@@ -878,12 +878,17 @@ acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId =
|
||||
Nothing -> do
|
||||
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
|
||||
connId <- withAgent $ \a -> prepareConnectionToAccept a True invId pqSup'
|
||||
(ct, conn) <- withStore' $ \db -> createAcceptedContact db user connId chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' False
|
||||
(ct, conn) <- withStore' $ \db -> 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
|
||||
case contactConn ct of
|
||||
Nothing -> throwChatError $ CECommandError "contact has no connection"
|
||||
Nothing -> do
|
||||
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
|
||||
connId <- withAgent $ \a -> prepareConnectionToAccept a True invId pqSup'
|
||||
currentTs <- liftIO getCurrentTime
|
||||
conn <- withStore' $ \db -> createAcceptedContactConn db user userContactLinkId contactId connId chatV cReqChatVRange pqSup' incognitoProfile subMode currentTs
|
||||
pure (ct {activeConn = Just conn}, conn, incognitoProfile)
|
||||
Just conn@Connection {customUserProfileId} -> do
|
||||
incognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId
|
||||
pure (ct, conn, ExistingIncognito <$> incognitoProfile)
|
||||
@@ -891,32 +896,41 @@ acceptContactRequest user@User {userId} UserContactRequest {agentInvitationId =
|
||||
dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend
|
||||
(ct,conn,) <$> withAgent (\a -> acceptContact a (aConnId conn) True invId dm pqSup' subMode)
|
||||
|
||||
acceptContactRequestAsync :: User -> UserContactRequest -> Maybe IncognitoProfile -> PQSupport -> CM Contact
|
||||
acceptContactRequestAsync user cReq@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile pqSup = do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let profileToSend = profileToSendOnAccept user incognitoProfile False
|
||||
vr <- chatVersionRange
|
||||
let chatV = vr `peerConnChatVersion` cReqChatVRange
|
||||
(cmdId, acId) <- agentAcceptContactAsync user True invId (XInfo profileToSend) subMode pqSup chatV
|
||||
withStore' $ \db -> do
|
||||
(ct, Connection {connId}) <- createAcceptedContact db user acId chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup True
|
||||
deleteContactRequestRec db user cReq
|
||||
setCommandConnId db user cmdId connId
|
||||
pure ct
|
||||
acceptContactRequestAsync :: User -> Int64 -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> Maybe IncognitoProfile -> CM Contact
|
||||
acceptContactRequestAsync
|
||||
user
|
||||
uclId
|
||||
cReqInvId
|
||||
cReqChatVRange
|
||||
cReqProfile
|
||||
cReqXContactId_
|
||||
cReqPQSup
|
||||
incognitoProfile = do
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let profileToSend = profileToSendOnAccept user incognitoProfile False
|
||||
vr <- chatVersionRange
|
||||
let chatV = vr `peerConnChatVersion` cReqChatVRange
|
||||
(cmdId, acId) <- agentAcceptContactAsync user True cReqInvId (XInfo profileToSend) subMode cReqPQSup chatV
|
||||
withStore $ \db -> do
|
||||
(ct, Connection {connId}) <- createAcceptedContact db vr user uclId acId chatV cReqChatVRange cReqProfile cReqXContactId_ cReqPQSup incognitoProfile subMode
|
||||
liftIO $ setCommandConnId db user cmdId connId
|
||||
pure ct
|
||||
|
||||
acceptGroupJoinRequestAsync :: User -> GroupInfo -> UserContactRequest -> GroupAcceptance -> GroupMemberRole -> Maybe IncognitoProfile -> CM GroupMember
|
||||
acceptGroupJoinRequestAsync :: User -> Int64 -> GroupInfo -> InvitationId -> VersionRangeChat -> Profile -> GroupAcceptance -> GroupMemberRole -> Maybe IncognitoProfile -> CM GroupMember
|
||||
acceptGroupJoinRequestAsync
|
||||
user
|
||||
uclId
|
||||
gInfo@GroupInfo {groupProfile, membership, businessChat}
|
||||
ucr@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange}
|
||||
cReqInvId
|
||||
cReqChatVRange
|
||||
cReqProfile
|
||||
gAccepted
|
||||
gLinkMemRole
|
||||
incognitoProfile = do
|
||||
gVar <- asks random
|
||||
let initialStatus = acceptanceToStatus (memberAdmission groupProfile) gAccepted
|
||||
(groupMemberId, memberId) <- withStore $ \db -> do
|
||||
liftIO $ deleteContactRequestRec db user ucr
|
||||
createJoiningMember db gVar user gInfo ucr gLinkMemRole initialStatus
|
||||
(groupMemberId, memberId) <- withStore $ \db ->
|
||||
createJoiningMember db gVar user gInfo cReqChatVRange cReqProfile gLinkMemRole initialStatus
|
||||
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
|
||||
let Profile {displayName} = profileToSendOnAccept user incognitoProfile True
|
||||
GroupMember {memberRole = userRole, memberId = userMemberId} = membership
|
||||
@@ -934,21 +948,23 @@ acceptGroupJoinRequestAsync
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
vr <- chatVersionRange
|
||||
let chatV = vr `peerConnChatVersion` cReqChatVRange
|
||||
connIds <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV
|
||||
connIds <- agentAcceptContactAsync user True cReqInvId msg subMode PQSupportOff chatV
|
||||
withStore $ \db -> do
|
||||
liftIO $ createJoiningMemberConnection db user connIds chatV ucr groupMemberId subMode
|
||||
liftIO $ createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode
|
||||
getGroupMemberById db vr user groupMemberId
|
||||
|
||||
acceptGroupJoinSendRejectAsync :: User -> GroupInfo -> UserContactRequest -> GroupRejectionReason -> CM GroupMember
|
||||
acceptGroupJoinSendRejectAsync :: User -> Int64 -> GroupInfo -> InvitationId -> VersionRangeChat -> Profile -> GroupRejectionReason -> CM GroupMember
|
||||
acceptGroupJoinSendRejectAsync
|
||||
user
|
||||
uclId
|
||||
gInfo@GroupInfo {groupProfile, membership}
|
||||
ucr@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange}
|
||||
cReqInvId
|
||||
cReqChatVRange
|
||||
cReqProfile
|
||||
rejectionReason = do
|
||||
gVar <- asks random
|
||||
(groupMemberId, memberId) <- withStore $ \db -> do
|
||||
liftIO $ deleteContactRequestRec db user ucr
|
||||
createJoiningMember db gVar user gInfo ucr GRObserver GSMemRejected
|
||||
(groupMemberId, memberId) <- withStore $ \db ->
|
||||
createJoiningMember db gVar user gInfo cReqChatVRange cReqProfile GRObserver GSMemRejected
|
||||
let GroupMember {memberRole = userRole, memberId = userMemberId} = membership
|
||||
msg =
|
||||
XGrpLinkReject $
|
||||
@@ -961,22 +977,25 @@ acceptGroupJoinSendRejectAsync
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
vr <- chatVersionRange
|
||||
let chatV = vr `peerConnChatVersion` cReqChatVRange
|
||||
connIds <- agentAcceptContactAsync user False invId msg subMode PQSupportOff chatV
|
||||
connIds <- agentAcceptContactAsync user False cReqInvId msg subMode PQSupportOff chatV
|
||||
withStore $ \db -> do
|
||||
liftIO $ createJoiningMemberConnection db user connIds chatV ucr groupMemberId subMode
|
||||
liftIO $ createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode
|
||||
getGroupMemberById db vr user groupMemberId
|
||||
|
||||
acceptBusinessJoinRequestAsync :: User -> UserContactRequest -> CM GroupInfo
|
||||
acceptBusinessJoinRequestAsync :: User -> Int64 -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> CM (GroupInfo, GroupMember)
|
||||
acceptBusinessJoinRequestAsync
|
||||
user
|
||||
ucr@UserContactRequest {contactRequestId, agentInvitationId = AgentInvId invId, cReqChatVRange} = do
|
||||
uclId
|
||||
cReqInvId
|
||||
cReqChatVRange
|
||||
cReqProfile
|
||||
cReqXContactId_ = do
|
||||
vr <- chatVersionRange
|
||||
gVar <- asks random
|
||||
let userProfile@Profile {displayName, preferences} = profileToSendOnAccept user Nothing True
|
||||
groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences
|
||||
(gInfo, clientMember) <- withStore $ \db -> do
|
||||
liftIO $ deleteContactRequest db user contactRequestId
|
||||
createBusinessRequestGroup db vr gVar user ucr groupPreferences
|
||||
(gInfo, clientMember) <- withStore $ \db ->
|
||||
createBusinessRequestGroup db vr gVar user cReqChatVRange cReqProfile cReqXContactId_ groupPreferences
|
||||
let GroupInfo {membership} = gInfo
|
||||
GroupMember {memberRole = userRole, memberId = userMemberId} = membership
|
||||
GroupMember {groupMemberId, memberId} = clientMember
|
||||
@@ -996,12 +1015,12 @@ acceptBusinessJoinRequestAsync
|
||||
}
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let chatV = vr `peerConnChatVersion` cReqChatVRange
|
||||
connIds <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV
|
||||
withStore' $ \db -> createJoiningMemberConnection db user connIds chatV ucr groupMemberId subMode
|
||||
connIds <- agentAcceptContactAsync user True cReqInvId msg subMode PQSupportOff chatV
|
||||
withStore' $ \db -> createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode
|
||||
let cd = CDGroupSnd gInfo Nothing
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
|
||||
createGroupFeatureItems user cd CISndGroupFeature gInfo
|
||||
pure gInfo
|
||||
pure (gInfo, clientMember)
|
||||
where
|
||||
businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile
|
||||
businessGroupProfile Profile {displayName, fullName, image} groupPreferences =
|
||||
|
||||
@@ -668,7 +668,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
_ -> pure ()
|
||||
where
|
||||
-- TODO [short links] don't send auto-reply message if it should have been created by connecting client
|
||||
-- TODO (based on version + whether address has short link data)
|
||||
-- TODO - based on version + whether address has short link data (shortLinkDataSet)
|
||||
sendAutoReply ct = \case
|
||||
Just AutoAccept {autoReply = Just mc} -> do
|
||||
(msg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing))
|
||||
@@ -1208,7 +1208,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
RcvChunkError -> badRcvFileChunk ft $ "incorrect chunk number " <> show chunkNo
|
||||
|
||||
processUserContactRequest :: AEvent e -> ConnectionEntity -> Connection -> UserContact -> CM ()
|
||||
processUserContactRequest agentMsg connEntity conn UserContact {userContactLinkId} = case agentMsg of
|
||||
processUserContactRequest agentMsg connEntity conn UserContact {userContactLinkId = uclId} = case agentMsg of
|
||||
REQ invId pqSupport _ connInfo -> do
|
||||
ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo
|
||||
case chatMsgEvent of
|
||||
@@ -1227,54 +1227,68 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
where
|
||||
profileContactRequest :: InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> Maybe MsgContent -> PQSupport -> CM ()
|
||||
profileContactRequest invId chatVRange p@Profile {displayName} xContactId_ mc_ reqPQSup = do
|
||||
-- TODO [short links] on contact request create contact with message
|
||||
-- TODO - instead of creating a contact request, create a contact that can be accepted or rejected,
|
||||
-- TODO and can be opened as a chat to view message
|
||||
-- TODO - see schema comments on persistence
|
||||
withStore (\db -> createOrUpdateContactRequest db vr user userContactLinkId invId chatVRange p xContactId_ reqPQSup) >>= \case
|
||||
CORContact contact -> toView $ CEvtContactRequestAlreadyAccepted user contact
|
||||
CORGroup gInfo -> toView $ CEvtBusinessRequestAlreadyAccepted user gInfo
|
||||
CORRequest cReq -> do
|
||||
ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId
|
||||
let (UserContactLink {connLinkContact = CCLink connReq _, autoAccept}, gLinkInfo_) = ucl
|
||||
isSimplexTeam = sameConnReqContact connReq adminContactReq
|
||||
v = maxVersion chatVRange
|
||||
case autoAccept of
|
||||
Just AutoAccept {acceptIncognito, businessAddress}
|
||||
| businessAddress ->
|
||||
if isSimplexTeam && v < businessChatsVersion
|
||||
then do
|
||||
ct <- acceptContactRequestAsync user cReq Nothing reqPQSup
|
||||
ucl <- withStore $ \db -> getUserContactLinkById db userId uclId
|
||||
let (UserContactLink {connLinkContact = CCLink connReq _, autoAccept}, gLinkInfo_) = ucl
|
||||
isSimplexTeam = sameConnReqContact connReq adminContactReq
|
||||
v = maxVersion chatVRange
|
||||
case autoAccept of
|
||||
Nothing ->
|
||||
withStore (\db -> createOrUpdateContactRequest db vr user uclId invId chatVRange p xContactId_ reqPQSup) >>= \case
|
||||
CORContact ct -> toView $ CEvtContactRequestAlreadyAccepted user ct
|
||||
CORRequest cReq ct_ -> do
|
||||
forM_ ct_ $ \ct ->
|
||||
forM_ mc_ $ \mc ->
|
||||
createInternalChatItem user (CDDirectRcv ct) (CIRcvMsgContent mc) Nothing
|
||||
toView $ CEvtReceivedContactRequest user cReq ct_
|
||||
Just AutoAccept {acceptIncognito, businessAddress}
|
||||
| businessAddress ->
|
||||
if isSimplexTeam && v < businessChatsVersion
|
||||
then
|
||||
maybe (pure Nothing) (\xContactId -> withStore' (\db -> getAcceptedContactByXContactId db vr user xContactId)) xContactId_ >>= \case
|
||||
Just ct -> toView $ CEvtContactRequestAlreadyAccepted user ct
|
||||
Nothing -> do
|
||||
ct <- acceptContactRequestAsync user uclId invId chatVRange p xContactId_ reqPQSup Nothing
|
||||
forM_ mc_ $ \mc ->
|
||||
createInternalChatItem user (CDDirectRcv ct) (CIRcvMsgContent mc) Nothing
|
||||
toView $ CEvtAcceptingContactRequest user ct
|
||||
else do
|
||||
gInfo <- acceptBusinessJoinRequestAsync user cReq
|
||||
else
|
||||
maybe (pure Nothing) (\xContactId -> withStore' (\db -> getAcceptedBusinessChatByXContactId db vr user xContactId)) xContactId_ >>= \case
|
||||
Just gInfo -> toView $ CEvtBusinessRequestAlreadyAccepted user gInfo
|
||||
Nothing -> do
|
||||
(gInfo, clientMember) <- acceptBusinessJoinRequestAsync user uclId invId chatVRange p xContactId_
|
||||
forM_ mc_ $ \mc ->
|
||||
createInternalChatItem user (CDGroupRcv gInfo Nothing clientMember) (CIRcvMsgContent mc) Nothing
|
||||
toView $ CEvtAcceptingBusinessRequest user gInfo
|
||||
| otherwise -> case gLinkInfo_ of
|
||||
| otherwise -> case gLinkInfo_ of
|
||||
Nothing ->
|
||||
maybe (pure Nothing) (\xContactId -> withStore' (\db -> getAcceptedContactByXContactId db vr user xContactId)) xContactId_ >>= \case
|
||||
Just ct -> toView $ CEvtContactRequestAlreadyAccepted user ct
|
||||
Nothing -> do
|
||||
-- [incognito] generate profile to send, create connection with incognito profile
|
||||
incognitoProfile <- if acceptIncognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
|
||||
ct <- acceptContactRequestAsync user cReq incognitoProfile reqPQSup
|
||||
ct <- acceptContactRequestAsync user uclId invId chatVRange p xContactId_ reqPQSup incognitoProfile
|
||||
forM_ mc_ $ \mc ->
|
||||
createInternalChatItem user (CDDirectRcv ct) (CIRcvMsgContent mc) Nothing
|
||||
toView $ CEvtAcceptingContactRequest user ct
|
||||
Just gli@GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do
|
||||
gInfo <- withStore $ \db -> getGroupInfo db vr user groupId
|
||||
acceptMember_ <- asks $ acceptMember . chatHooks . config
|
||||
maybe (pure $ Right (GAAccepted, gLinkMemRole)) (\am -> liftIO $ am gInfo gli p) acceptMember_ >>= \case
|
||||
Right (acceptance, useRole)
|
||||
| v < groupFastLinkJoinVersion ->
|
||||
messageError "processUserContactRequest: chat version range incompatible for accepting group join request"
|
||||
| otherwise -> do
|
||||
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo
|
||||
mem <- acceptGroupJoinRequestAsync user gInfo cReq acceptance useRole profileMode
|
||||
(gInfo', mem', scopeInfo) <- mkGroupChatScope gInfo mem
|
||||
createInternalChatItem user (CDGroupRcv gInfo' scopeInfo mem') (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing
|
||||
toView $ CEvtAcceptingGroupJoinRequestMember user gInfo' mem'
|
||||
Left rjctReason
|
||||
| v < groupJoinRejectVersion ->
|
||||
messageWarning $ "processUserContactRequest (group " <> groupName' gInfo <> "): joining of " <> displayName <> " is blocked"
|
||||
| otherwise -> do
|
||||
mem <- acceptGroupJoinSendRejectAsync user gInfo cReq rjctReason
|
||||
toViewTE $ TERejectingGroupJoinRequestMember user gInfo mem rjctReason
|
||||
_ -> toView $ CEvtReceivedContactRequest user cReq
|
||||
Just gli@GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do
|
||||
gInfo <- withStore $ \db -> getGroupInfo db vr user groupId
|
||||
acceptMember_ <- asks $ acceptMember . chatHooks . config
|
||||
maybe (pure $ Right (GAAccepted, gLinkMemRole)) (\am -> liftIO $ am gInfo gli p) acceptMember_ >>= \case
|
||||
Right (acceptance, useRole)
|
||||
| v < groupFastLinkJoinVersion ->
|
||||
messageError "processUserContactRequest: chat version range incompatible for accepting group join request"
|
||||
| otherwise -> do
|
||||
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo
|
||||
mem <- acceptGroupJoinRequestAsync user uclId gInfo invId chatVRange p acceptance useRole profileMode
|
||||
(gInfo', mem', scopeInfo) <- mkGroupChatScope gInfo mem
|
||||
createInternalChatItem user (CDGroupRcv gInfo' scopeInfo mem') (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing
|
||||
toView $ CEvtAcceptingGroupJoinRequestMember user gInfo' mem'
|
||||
Left rjctReason
|
||||
| v < groupJoinRejectVersion ->
|
||||
messageWarning $ "processUserContactRequest (group " <> groupName' gInfo <> "): joining of " <> displayName <> " is blocked"
|
||||
| otherwise -> do
|
||||
mem <- acceptGroupJoinSendRejectAsync user uclId gInfo invId chatVRange p rjctReason
|
||||
toViewTE $ TERejectingGroupJoinRequestMember user gInfo mem rjctReason
|
||||
|
||||
memberCanSend :: GroupMember -> Maybe MsgScope -> CM () -> CM ()
|
||||
memberCanSend m@GroupMember {memberRole} msgScope a = case msgScope of
|
||||
|
||||
@@ -112,7 +112,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
|
||||
[sql|
|
||||
SELECT
|
||||
c.contact_profile_id, c.local_display_name, c.via_group, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite,
|
||||
p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect,
|
||||
p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.contact_request_id,
|
||||
c.contact_group_member_id, c.contact_grp_inv_sent, c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl
|
||||
FROM contacts c
|
||||
JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id
|
||||
@@ -120,13 +120,13 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
|
||||
|]
|
||||
(userId, contactId)
|
||||
toContact' :: Int64 -> Connection -> [ChatTagId] -> ContactRow' -> Contact
|
||||
toContact' contactId conn chatTags ((profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (connFullLink, connShortLink, contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) =
|
||||
toContact' contactId conn chatTags ((profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (connFullLink, connShortLink, contactRequestId, contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) =
|
||||
let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
|
||||
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
|
||||
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
|
||||
activeConn = Just conn
|
||||
connLinkToConnect = toACreatedConnLink_ connFullLink connShortLink
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, connLinkToConnect, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, connLinkToConnect, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
|
||||
getGroupAndMember_ :: Int64 -> Connection -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
getGroupAndMember_ groupMemberId c = do
|
||||
gm <-
|
||||
|
||||
@@ -57,11 +57,15 @@ module Simplex.Chat.Store.Direct
|
||||
setQuotaErrCounter,
|
||||
getUserContacts,
|
||||
createOrUpdateContactRequest,
|
||||
getAcceptedContactByXContactId,
|
||||
getAcceptedBusinessChatByXContactId,
|
||||
getUserContactLinkIdByCReq,
|
||||
getContactRequest',
|
||||
getContactRequest,
|
||||
getContactRequestIdByName,
|
||||
deleteContactRequest,
|
||||
createContactFromRequest,
|
||||
createAcceptedContactConn,
|
||||
createAcceptedContact,
|
||||
deleteContactRequestRec,
|
||||
updateContactAccepted,
|
||||
@@ -107,7 +111,6 @@ import Simplex.Messaging.Agent.Store.DB (Binary (..), BoolInt (..))
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
import Simplex.Messaging.Crypto.Ratchet (PQSupport)
|
||||
import Simplex.Messaging.Protocol (SubscriptionMode (..))
|
||||
import Simplex.Messaging.Util ((<$$>))
|
||||
import Simplex.Messaging.Version
|
||||
#if defined(dbPostgres)
|
||||
import Database.PostgreSQL.Simple (Only (..), (:.) (..))
|
||||
@@ -206,7 +209,7 @@ getContactByConnReqHash db vr user@User {userId} cReqHash = do
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.contact_request_id,
|
||||
ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
@@ -269,6 +272,7 @@ createPreparedContact db user@User {userId} p@Profile {preferences} connLinkToCo
|
||||
updatedAt = currentTs,
|
||||
chatTs = Just currentTs,
|
||||
connLinkToConnect = Just connLinkToConnect,
|
||||
contactRequestId = Nothing,
|
||||
contactGroupMemberId = Nothing,
|
||||
contactGrpInvSent = False,
|
||||
chatTags = [],
|
||||
@@ -302,6 +306,7 @@ createDirectContact db user@User {userId} conn@Connection {connId, localAlias} p
|
||||
updatedAt = currentTs,
|
||||
chatTs = Just currentTs,
|
||||
connLinkToConnect = Nothing,
|
||||
contactRequestId = Nothing,
|
||||
contactGroupMemberId = Nothing,
|
||||
contactGrpInvSent = False,
|
||||
chatTags = [],
|
||||
@@ -648,24 +653,36 @@ getUserContacts db vr user@User {userId} = do
|
||||
pure $ filter (\Contact {activeConn} -> isJust activeConn) contacts
|
||||
|
||||
createOrUpdateContactRequest :: DB.Connection -> VersionRangeChat -> User -> Int64 -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> ExceptT StoreError IO ChatOrRequest
|
||||
createOrUpdateContactRequest db vr user@User {userId, userContactId} userContactLinkId invId (VersionRange minV maxV) Profile {displayName, fullName, image, contactLink, preferences} xContactId_ pqSup =
|
||||
liftIO (maybeM getContactOrGroup xContactId_) >>= \case
|
||||
Just cr -> pure cr
|
||||
Nothing -> CORRequest <$> createOrUpdate_
|
||||
createOrUpdateContactRequest
|
||||
db
|
||||
vr
|
||||
user@User {userId}
|
||||
uclId
|
||||
invId
|
||||
(VersionRange minV maxV)
|
||||
Profile {displayName, fullName, image, contactLink, preferences}
|
||||
xContactId_
|
||||
pqSup =
|
||||
-- this doesn't return a newly created contact request with contact,
|
||||
-- because we only set xContactId after contact is accepted
|
||||
-- (this allows older clients to update contact request by reusing xContactId)
|
||||
liftIO (maybeM (getAcceptedContactByXContactId db vr user) xContactId_) >>= \case
|
||||
Just ct -> pure $ CORContact ct
|
||||
Nothing -> do
|
||||
(ucr, ct_) <- createOrUpdateRequest
|
||||
pure $ CORRequest ucr ct_
|
||||
where
|
||||
maybeM = maybe (pure Nothing)
|
||||
getContactOrGroup xContactId =
|
||||
getContact' xContactId >>= \case
|
||||
Just ct -> pure $ Just $ CORContact ct
|
||||
Nothing -> CORGroup <$$> getGroupInfo' xContactId
|
||||
createOrUpdate_ :: ExceptT StoreError IO UserContactRequest
|
||||
createOrUpdate_ = do
|
||||
createOrUpdateRequest :: ExceptT StoreError IO (UserContactRequest, Maybe Contact)
|
||||
createOrUpdateRequest = do
|
||||
cReqId <-
|
||||
ExceptT $
|
||||
maybeM getContactRequestByXContactId xContactId_ >>= \case
|
||||
Nothing -> createContactRequest
|
||||
Just cr@UserContactRequest {contactRequestId} -> updateContactRequest cr $> Right contactRequestId
|
||||
getContactRequest db user cReqId
|
||||
ucr@UserContactRequest {contactId_} <- getContactRequest db user cReqId
|
||||
ct_ <- forM contactId_ $ \contactId -> getContact db vr user contactId
|
||||
pure (ucr, ct_)
|
||||
createContactRequest :: IO (Either StoreError Int64)
|
||||
createContactRequest = do
|
||||
currentTs <- getCurrentTime
|
||||
@@ -685,44 +702,20 @@ createOrUpdateContactRequest db vr user@User {userId, userContactId} userContact
|
||||
created_at, updated_at, xcontact_id, pq_support)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
( (userContactLinkId, Binary invId, minV, maxV, profileId, ldn, userId)
|
||||
( (uclId, Binary invId, minV, maxV, profileId, ldn, userId)
|
||||
:. (currentTs, currentTs, xContactId_, pqSup)
|
||||
)
|
||||
insertedRowId db
|
||||
getContact' :: XContactId -> IO (Maybe Contact)
|
||||
getContact' xContactId = do
|
||||
ct_ <-
|
||||
maybeFirstRow (toContact vr user []) $
|
||||
DB.query
|
||||
contactRequestId <- insertedRowId db
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect,
|
||||
ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
|
||||
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||
WHERE ct.user_id = ? AND ct.xcontact_id = ? AND ct.deleted = 0
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT 1
|
||||
|]
|
||||
(userId, xContactId)
|
||||
mapM (addDirectChatTags db) ct_
|
||||
getGroupInfo' :: XContactId -> IO (Maybe GroupInfo)
|
||||
getGroupInfo' xContactId = do
|
||||
g_ <-
|
||||
maybeFirstRow (toGroupInfo vr userContactId []) $
|
||||
DB.query
|
||||
"INSERT INTO contacts (contact_profile_id, local_display_name, user_id, created_at, updated_at, chat_ts, contact_used, contact_request_id) VALUES (?,?,?,?,?,?,?,?)"
|
||||
(profileId, ldn, userId, currentTs, currentTs, currentTs, BI True, contactRequestId)
|
||||
contactId <- insertedRowId db
|
||||
DB.execute
|
||||
db
|
||||
(groupInfoQuery <> " WHERE g.business_xcontact_id = ? AND g.user_id = ? AND mu.contact_id = ?")
|
||||
(xContactId, userId, userContactId)
|
||||
mapM (addGroupChatTags db) g_
|
||||
"UPDATE contact_requests SET contact_id = ? WHERE contact_request_id = ?"
|
||||
(contactId, contactRequestId)
|
||||
pure contactRequestId
|
||||
getContactRequestByXContactId :: XContactId -> IO (Maybe UserContactRequest)
|
||||
getContactRequestByXContactId xContactId =
|
||||
maybeFirstRow toContactRequest $
|
||||
@@ -742,7 +735,7 @@ createOrUpdateContactRequest db vr user@User {userId, userContactId} userContact
|
||||
|]
|
||||
(userId, xContactId)
|
||||
updateContactRequest :: UserContactRequest -> IO (Either StoreError ())
|
||||
updateContactRequest UserContactRequest {contactRequestId = cReqId, localDisplayName = oldLdn, profile = Profile {displayName = oldDisplayName}} = do
|
||||
updateContactRequest UserContactRequest {contactRequestId = cReqId, contactId_, localDisplayName = oldLdn, profile = Profile {displayName = oldDisplayName}} = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
updateProfile currentTs
|
||||
if displayName == oldDisplayName
|
||||
@@ -766,6 +759,15 @@ createOrUpdateContactRequest db vr user@User {userId, userContactId} userContact
|
||||
WHERE user_id = ? AND contact_request_id = ?
|
||||
|]
|
||||
(Binary invId, pqSup, minV, maxV, ldn, currentTs, userId, cReqId)
|
||||
forM_ contactId_ $ \contactId ->
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE contacts
|
||||
SET local_display_name = ?, updated_at = ?
|
||||
WHERE contact_id = ?
|
||||
|]
|
||||
(ldn, currentTs, contactId)
|
||||
safeDeleteLDN db user oldLdn
|
||||
where
|
||||
updateProfile currentTs =
|
||||
@@ -787,6 +789,42 @@ createOrUpdateContactRequest db vr user@User {userId, userContactId} userContact
|
||||
|]
|
||||
(displayName, fullName, image, contactLink, currentTs, userId, cReqId)
|
||||
|
||||
getAcceptedContactByXContactId :: DB.Connection -> VersionRangeChat -> User -> XContactId -> IO (Maybe Contact)
|
||||
getAcceptedContactByXContactId db vr user@User {userId} xContactId = do
|
||||
ct_ <-
|
||||
maybeFirstRow (toContact vr user []) $
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.contact_request_id,
|
||||
ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
|
||||
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||
WHERE ct.user_id = ? AND ct.xcontact_id = ? AND ct.deleted = 0
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT 1
|
||||
|]
|
||||
(userId, xContactId)
|
||||
mapM (addDirectChatTags db) ct_
|
||||
|
||||
getAcceptedBusinessChatByXContactId :: DB.Connection -> VersionRangeChat -> User -> XContactId -> IO (Maybe GroupInfo)
|
||||
getAcceptedBusinessChatByXContactId db vr User {userId, userContactId} xContactId = do
|
||||
g_ <-
|
||||
maybeFirstRow (toGroupInfo vr userContactId []) $
|
||||
DB.query
|
||||
db
|
||||
(groupInfoQuery <> " WHERE g.business_xcontact_id = ? AND g.user_id = ? AND mu.contact_id = ?")
|
||||
(xContactId, userId, userContactId)
|
||||
mapM (addGroupChatTags db) g_
|
||||
|
||||
getUserContactLinkIdByCReq :: DB.Connection -> Int64 -> ExceptT StoreError IO Int64
|
||||
getUserContactLinkIdByCReq db contactRequestId =
|
||||
ExceptT . firstRow fromOnly (SEContactRequestNotFound contactRequestId) $
|
||||
@@ -846,20 +884,17 @@ deleteContactRequest db User {userId} contactRequestId = do
|
||||
(userId, userId, contactRequestId, userId)
|
||||
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
|
||||
|
||||
createAcceptedContact :: DB.Connection -> User -> ConnId -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO (Contact, Connection)
|
||||
createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId connChatVersion cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed = do
|
||||
createdAt <- getCurrentTime
|
||||
customUserProfileId <- forM incognitoProfile $ \case
|
||||
NewIncognito p -> createIncognitoProfile_ db userId createdAt p
|
||||
ExistingIncognito LocalProfile {profileId = pId} -> pure pId
|
||||
createContactFromRequest :: DB.Connection -> User -> Int64 -> ConnId -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO (Contact, Connection)
|
||||
createContactFromRequest db user@User {userId, profile = LocalProfile {preferences}} uclId agentConnId connChatVersion cReqChatVRange localDisplayName profileId profile xContactId incognitoProfile subMode pqSup contactUsed = do
|
||||
currentTs <- getCurrentTime
|
||||
let userPreferences = fromMaybe emptyChatPrefs $ incognitoProfile >> preferences
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id, contact_used) VALUES (?,?,?,?,?,?,?,?,?,?)"
|
||||
(userId, localDisplayName, profileId, BI True, userPreferences, createdAt, createdAt, createdAt, xContactId, BI contactUsed)
|
||||
(userId, localDisplayName, profileId, BI True, userPreferences, currentTs, currentTs, currentTs, xContactId, BI contactUsed)
|
||||
contactId <- insertedRowId db
|
||||
DB.execute db "UPDATE contact_requests SET contact_id = ? WHERE user_id = ? AND local_display_name = ?" (contactId, userId, localDisplayName)
|
||||
conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup
|
||||
conn <- createAcceptedContactConn db user uclId contactId agentConnId connChatVersion cReqChatVRange pqSup incognitoProfile subMode currentTs
|
||||
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn
|
||||
ct =
|
||||
Contact
|
||||
@@ -873,10 +908,11 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}
|
||||
chatSettings = defaultChatSettings,
|
||||
userPreferences,
|
||||
mergedPreferences,
|
||||
createdAt,
|
||||
updatedAt = createdAt,
|
||||
chatTs = Just createdAt,
|
||||
createdAt = currentTs,
|
||||
updatedAt = currentTs,
|
||||
chatTs = Just currentTs,
|
||||
connLinkToConnect = Nothing,
|
||||
contactRequestId = Nothing,
|
||||
contactGroupMemberId = Nothing,
|
||||
contactGrpInvSent = False,
|
||||
chatTags = [],
|
||||
@@ -887,6 +923,44 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}
|
||||
}
|
||||
pure (ct, conn)
|
||||
|
||||
createAcceptedContactConn :: DB.Connection -> User -> Int64 -> ContactId -> ConnId -> VersionChat -> VersionRangeChat -> PQSupport -> Maybe IncognitoProfile -> SubscriptionMode -> UTCTime -> IO Connection
|
||||
createAcceptedContactConn db User {userId} uclId contactId agentConnId connChatVersion cReqChatVRange pqSup incognitoProfile subMode currentTs = do
|
||||
customUserProfileId <- forM incognitoProfile $ \case
|
||||
NewIncognito p -> createIncognitoProfile_ db userId currentTs p
|
||||
ExistingIncognito LocalProfile {profileId = pId} -> pure pId
|
||||
createConnection_ db userId ConnContact (Just contactId) agentConnId ConnNew connChatVersion cReqChatVRange Nothing (Just uclId) customUserProfileId 0 currentTs subMode pqSup
|
||||
|
||||
createAcceptedContact :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> Maybe IncognitoProfile -> SubscriptionMode -> ExceptT StoreError IO (Contact, Connection)
|
||||
createAcceptedContact
|
||||
db
|
||||
vr
|
||||
user@User {userId}
|
||||
uclId
|
||||
agentConnId
|
||||
connChatVersion
|
||||
cReqChatVRange
|
||||
Profile {displayName, fullName, image, contactLink, preferences}
|
||||
xContactId
|
||||
pqSup
|
||||
incognitoProfile
|
||||
subMode = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
let userPreferences = fromMaybe emptyChatPrefs $ incognitoProfile >> preferences
|
||||
contactId <- ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)"
|
||||
(displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs)
|
||||
profileId <- insertedRowId db
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id, contact_used) VALUES (?,?,?,?,?,?,?,?,?,?)"
|
||||
(userId, ldn, profileId, BI True, userPreferences, currentTs, currentTs, currentTs, xContactId, BI True)
|
||||
Right <$> insertedRowId db
|
||||
conn <- liftIO $ createAcceptedContactConn db user uclId contactId agentConnId connChatVersion cReqChatVRange pqSup incognitoProfile subMode currentTs
|
||||
ct <- getContact db vr user contactId
|
||||
pure (ct, conn)
|
||||
|
||||
deleteContactRequestRec :: DB.Connection -> User -> UserContactRequest -> IO ()
|
||||
deleteContactRequestRec db User {userId} UserContactRequest {contactRequestId} =
|
||||
DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId)
|
||||
@@ -916,7 +990,7 @@ getContact_ db vr user@User {userId} contactId deleted = do
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.contact_request_id,
|
||||
ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
|
||||
@@ -1183,23 +1183,31 @@ createNewContactMemberAsync db gVar user@User {userId, userContactId} GroupInfo
|
||||
:. (minV, maxV)
|
||||
)
|
||||
|
||||
createJoiningMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> UserContactRequest -> GroupMemberRole -> GroupMemberStatus -> ExceptT StoreError IO (GroupMemberId, MemberId)
|
||||
createJoiningMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> VersionRangeChat -> Profile -> GroupMemberRole -> GroupMemberStatus -> ExceptT StoreError IO (GroupMemberId, MemberId)
|
||||
createJoiningMember
|
||||
db
|
||||
gVar
|
||||
User {userId, userContactId}
|
||||
GroupInfo {groupId, membership}
|
||||
UserContactRequest {cReqChatVRange, localDisplayName, profileId}
|
||||
cReqChatVRange
|
||||
Profile {displayName, fullName, image, contactLink, preferences}
|
||||
memberRole
|
||||
memberStatus =
|
||||
createWithRandomId gVar $ \memId -> do
|
||||
createdAt <- liftIO getCurrentTime
|
||||
insertMember_ (MemberId memId) createdAt
|
||||
groupMemberId <- liftIO $ insertedRowId db
|
||||
pure (groupMemberId, MemberId memId)
|
||||
memberStatus = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
ExceptT . withLocalDisplayName db userId displayName $ \ldn -> runExceptT $ do
|
||||
liftIO $
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)"
|
||||
(displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs)
|
||||
profileId <- liftIO $ insertedRowId db
|
||||
createWithRandomId gVar $ \memId -> do
|
||||
insertMember_ ldn profileId (MemberId memId) currentTs
|
||||
groupMemberId <- liftIO $ insertedRowId db
|
||||
pure (groupMemberId, MemberId memId)
|
||||
where
|
||||
VersionRange minV maxV = cReqChatVRange
|
||||
insertMember_ memberId createdAt =
|
||||
insertMember_ ldn profileId memberId currentTs =
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
@@ -1210,30 +1218,33 @@ createJoiningMember
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
( (groupId, memberId, memberRole, GCInviteeMember, memberStatus, fromInvitedBy userContactId IBUser, groupMemberId' membership)
|
||||
:. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, createdAt, createdAt)
|
||||
:. (userId, ldn, Nothing :: (Maybe Int64), profileId, currentTs, currentTs)
|
||||
:. (minV, maxV)
|
||||
)
|
||||
|
||||
createJoiningMemberConnection :: DB.Connection -> User -> (CommandId, ConnId) -> VersionChat -> UserContactRequest -> GroupMemberId -> SubscriptionMode -> IO ()
|
||||
createJoiningMemberConnection :: DB.Connection -> User -> Int64 -> (CommandId, ConnId) -> VersionChat -> VersionRangeChat -> GroupMemberId -> SubscriptionMode -> IO ()
|
||||
createJoiningMemberConnection
|
||||
db
|
||||
user@User {userId}
|
||||
uclId
|
||||
(cmdId, agentConnId)
|
||||
chatV
|
||||
UserContactRequest {cReqChatVRange, userContactLinkId}
|
||||
cReqChatVRange
|
||||
groupMemberId
|
||||
subMode = do
|
||||
createdAt <- liftIO getCurrentTime
|
||||
Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId ConnNew chatV cReqChatVRange Nothing (Just userContactLinkId) Nothing 0 createdAt subMode PQSupportOff
|
||||
Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId ConnNew chatV cReqChatVRange Nothing (Just uclId) Nothing 0 createdAt subMode PQSupportOff
|
||||
setCommandConnId db user cmdId connId
|
||||
|
||||
createBusinessRequestGroup :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> UserContactRequest -> GroupPreferences -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
createBusinessRequestGroup :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> VersionRangeChat -> Profile -> Maybe XContactId -> GroupPreferences -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
createBusinessRequestGroup
|
||||
db
|
||||
vr
|
||||
gVar
|
||||
user@User {userId, userContactId}
|
||||
UserContactRequest {cReqChatVRange, xContactId, profile = Profile {displayName, fullName, image, contactLink, preferences}}
|
||||
cReqChatVRange
|
||||
Profile {displayName, fullName, image, contactLink, preferences}
|
||||
xContactId
|
||||
groupPreferences = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
(groupId, membership@GroupMember {memberId = userMemberId}) <- insertGroup_ currentTs
|
||||
@@ -2435,7 +2446,7 @@ createMemberContact
|
||||
quotaErrCounter = 0
|
||||
}
|
||||
mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn
|
||||
pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, connLinkToConnect = Nothing, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
|
||||
pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, connLinkToConnect = Nothing, contactRequestId = Nothing, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
|
||||
|
||||
getMemberContact :: DB.Connection -> VersionRangeChat -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation)
|
||||
getMemberContact db vr user contactId = do
|
||||
@@ -2472,7 +2483,7 @@ createMemberContactInvited
|
||||
contactId <- createContactUpdateMember currentTs userPreferences
|
||||
ctConn <- createMemberContactConn_ db user connIds gInfo mConn contactId subMode
|
||||
let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn
|
||||
mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, connLinkToConnect = Nothing, contactGroupMemberId = Nothing, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
|
||||
mCt' = Contact {contactId, localDisplayName = memberLDN, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, connLinkToConnect = Nothing, contactRequestId = Nothing, contactGroupMemberId = Nothing, contactGrpInvSent = False, chatTags = [], chatItemTTL = Nothing, uiThemes = Nothing, chatDeleted = False, customData = Nothing}
|
||||
m' = m {memberContactId = Just contactId}
|
||||
pure (mCt', m')
|
||||
where
|
||||
|
||||
@@ -1061,6 +1061,7 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of
|
||||
AND uc.user_id = ?
|
||||
AND uc.local_display_name = ''
|
||||
AND uc.group_id IS NULL
|
||||
AND cr.contact_id IS NULL
|
||||
AND (
|
||||
LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%'
|
||||
OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%'
|
||||
|
||||
@@ -5,15 +5,15 @@ module Simplex.Chat.Store.SQLite.Migrations.M20250526_short_links where
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
-- TODO [short links] contacts with contact requests
|
||||
-- TODO - contacts.is_contact_request flag?
|
||||
-- TODO - link contact_requests and contacts?
|
||||
m20250526_short_links :: Query
|
||||
m20250526_short_links =
|
||||
[sql|
|
||||
ALTER TABLE contacts ADD COLUMN conn_full_link_to_connect BLOB;
|
||||
ALTER TABLE contacts ADD COLUMN conn_short_link_to_connect BLOB;
|
||||
|
||||
ALTER TABLE contacts ADD COLUMN contact_request_id INTEGER REFERENCES contact_requests ON DELETE SET NULL;
|
||||
CREATE INDEX idx_contacts_contact_request_id ON contacts(contact_request_id);
|
||||
|
||||
ALTER TABLE groups ADD COLUMN conn_full_link_to_connect BLOB;
|
||||
ALTER TABLE groups ADD COLUMN conn_short_link_to_connect BLOB;
|
||||
ALTER TABLE groups ADD COLUMN conn_link_started_connection INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -25,6 +25,9 @@ down_m20250526_short_links =
|
||||
ALTER TABLE contacts DROP COLUMN conn_full_link_to_connect;
|
||||
ALTER TABLE contacts DROP COLUMN conn_short_link_to_connect;
|
||||
|
||||
DROP INDEX idx_contacts_contact_request_id;
|
||||
ALTER TABLE contacts DROP COLUMN contact_request_id;
|
||||
|
||||
ALTER TABLE groups DROP COLUMN conn_full_link_to_connect;
|
||||
ALTER TABLE groups DROP COLUMN conn_short_link_to_connect;
|
||||
ALTER TABLE groups DROP COLUMN conn_link_started_connection;
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
Query:
|
||||
UPDATE contacts
|
||||
SET local_display_name = ?, updated_at = ?
|
||||
WHERE contact_id = ?
|
||||
|
||||
Plan:
|
||||
SEARCH contacts USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE groups
|
||||
SET chat_ts = ?,
|
||||
@@ -169,29 +177,6 @@ Query:
|
||||
|
||||
Plan:
|
||||
|
||||
Query:
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect,
|
||||
ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
|
||||
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||
WHERE ct.user_id = ? AND ct.xcontact_id = ? AND ct.deleted = 0
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT 1
|
||||
|
||||
Plan:
|
||||
SEARCH ct USING INDEX idx_contacts_chat_ts (user_id=?)
|
||||
SEARCH cp USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH c USING INDEX idx_connections_contact_id (contact_id=?) LEFT-JOIN
|
||||
USE TEMP B-TREE FOR ORDER BY
|
||||
|
||||
Query:
|
||||
SELECT COUNT(1)
|
||||
FROM chat_items i
|
||||
@@ -342,7 +327,7 @@ Plan:
|
||||
Query:
|
||||
SELECT
|
||||
c.contact_profile_id, c.local_display_name, c.via_group, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite,
|
||||
p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect,
|
||||
p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.contact_request_id,
|
||||
c.contact_group_member_id, c.contact_grp_inv_sent, c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl
|
||||
FROM contacts c
|
||||
JOIN contact_profiles p ON c.contact_profile_id = p.contact_profile_id
|
||||
@@ -859,7 +844,7 @@ Query:
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.contact_request_id,
|
||||
ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
@@ -878,6 +863,29 @@ SEARCH ct USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH cp USING INTEGER PRIMARY KEY (rowid=?)
|
||||
USE TEMP B-TREE FOR ORDER BY
|
||||
|
||||
Query:
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.contact_request_id,
|
||||
ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
|
||||
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
|
||||
FROM contacts ct
|
||||
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
|
||||
LEFT JOIN connections c ON c.contact_id = ct.contact_id
|
||||
WHERE ct.user_id = ? AND ct.xcontact_id = ? AND ct.deleted = 0
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT 1
|
||||
|
||||
Plan:
|
||||
SEARCH ct USING INDEX idx_contacts_chat_ts (user_id=?)
|
||||
SEARCH cp USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH c USING INDEX idx_connections_contact_id (contact_id=?) LEFT-JOIN
|
||||
USE TEMP B-TREE FOR ORDER BY
|
||||
|
||||
Query:
|
||||
SELECT
|
||||
-- GroupInfo
|
||||
@@ -1431,7 +1439,7 @@ Query:
|
||||
SELECT
|
||||
-- Contact
|
||||
ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect,
|
||||
cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.contact_request_id,
|
||||
ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl,
|
||||
-- Connection
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias,
|
||||
@@ -1543,6 +1551,7 @@ Query:
|
||||
AND uc.user_id = ?
|
||||
AND uc.local_display_name = ''
|
||||
AND uc.group_id IS NULL
|
||||
AND cr.contact_id IS NULL
|
||||
AND (
|
||||
LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%'
|
||||
OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%'
|
||||
@@ -1569,6 +1578,7 @@ Query:
|
||||
AND uc.user_id = ?
|
||||
AND uc.local_display_name = ''
|
||||
AND uc.group_id IS NULL
|
||||
AND cr.contact_id IS NULL
|
||||
AND (
|
||||
LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%'
|
||||
OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%'
|
||||
@@ -1595,6 +1605,7 @@ Query:
|
||||
AND uc.user_id = ?
|
||||
AND uc.local_display_name = ''
|
||||
AND uc.group_id IS NULL
|
||||
AND cr.contact_id IS NULL
|
||||
AND (
|
||||
LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%'
|
||||
OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%'
|
||||
@@ -5306,6 +5317,7 @@ SEARCH contacts USING COVERING INDEX idx_contacts_contact_profile_id (contact_pr
|
||||
Query: DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?
|
||||
Plan:
|
||||
SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH contacts USING COVERING INDEX idx_contacts_contact_request_id (contact_request_id=?)
|
||||
|
||||
Query: DELETE FROM contacts WHERE user_id = ? AND contact_id = ?
|
||||
Plan:
|
||||
@@ -5557,6 +5569,10 @@ Query: INSERT INTO contact_profiles (display_name, full_name, image, user_id, pr
|
||||
Plan:
|
||||
SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?)
|
||||
|
||||
Query: INSERT INTO contacts (contact_profile_id, local_display_name, user_id, created_at, updated_at, chat_ts, contact_used, contact_request_id) VALUES (?,?,?,?,?,?,?,?)
|
||||
Plan:
|
||||
SEARCH users USING COVERING INDEX sqlite_autoindex_users_1 (contact_id=?)
|
||||
|
||||
Query: INSERT INTO contacts (contact_profile_id, local_display_name, user_id, is_user, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?)
|
||||
Plan:
|
||||
SEARCH users USING COVERING INDEX sqlite_autoindex_users_1 (contact_id=?)
|
||||
@@ -5940,9 +5956,9 @@ Query: UPDATE connections SET to_subscribe = 0 WHERE to_subscribe = 1
|
||||
Plan:
|
||||
SEARCH connections USING INDEX idx_connections_to_subscribe (to_subscribe=?)
|
||||
|
||||
Query: UPDATE contact_requests SET contact_id = ? WHERE user_id = ? AND local_display_name = ?
|
||||
Query: UPDATE contact_requests SET contact_id = ? WHERE contact_request_id = ?
|
||||
Plan:
|
||||
SEARCH contact_requests USING INDEX sqlite_autoindex_contact_requests_1 (user_id=? AND local_display_name=?)
|
||||
SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE contacts SET chat_deleted = ?, updated_at = ? WHERE user_id = ? AND contact_id = ?
|
||||
Plan:
|
||||
|
||||
@@ -81,6 +81,7 @@ CREATE TABLE contacts(
|
||||
chat_item_ttl INTEGER,
|
||||
conn_full_link_to_connect BLOB,
|
||||
conn_short_link_to_connect BLOB,
|
||||
contact_request_id INTEGER REFERENCES contact_requests ON DELETE SET NULL,
|
||||
FOREIGN KEY(user_id, local_display_name)
|
||||
REFERENCES display_names(user_id, local_display_name)
|
||||
ON DELETE CASCADE
|
||||
@@ -1053,3 +1054,4 @@ CREATE INDEX idx_chat_items_group_scope_item_status ON chat_items(
|
||||
item_status,
|
||||
item_ts
|
||||
);
|
||||
CREATE INDEX idx_contacts_contact_request_id ON contacts(contact_request_id);
|
||||
|
||||
@@ -46,7 +46,6 @@ import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..))
|
||||
import qualified Simplex.Messaging.Crypto.Ratchet as CR
|
||||
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (SubscriptionMode (..))
|
||||
import Simplex.Messaging.Util (allFinally)
|
||||
import Simplex.Messaging.Version
|
||||
import UnliftIO.STM
|
||||
#if defined(dbPostgres)
|
||||
@@ -178,10 +177,6 @@ handleSQLError err e
|
||||
| constraintError e = err
|
||||
| otherwise = SEInternalError $ show e
|
||||
|
||||
storeFinally :: ExceptT StoreError IO a -> ExceptT StoreError IO b -> ExceptT StoreError IO a
|
||||
storeFinally = allFinally mkStoreError
|
||||
{-# INLINE storeFinally #-}
|
||||
|
||||
mkStoreError :: E.SomeException -> StoreError
|
||||
mkStoreError = SEInternalError . show
|
||||
{-# INLINE mkStoreError #-}
|
||||
@@ -426,19 +421,19 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId =
|
||||
|]
|
||||
(userId, profileId, userId, profileId, userId, profileId)
|
||||
|
||||
type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe AConnectionRequestUri, Maybe AConnShortLink, Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64)
|
||||
type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. (Maybe AConnectionRequestUri, Maybe AConnShortLink, Maybe Int64, Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64)
|
||||
|
||||
type ContactRow = Only ContactId :. ContactRow'
|
||||
|
||||
toContact :: VersionRangeChat -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact
|
||||
toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (connFullLink, connShortLink, contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) :. connRow) =
|
||||
toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. (connFullLink, connShortLink, contactRequestId, contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) :. connRow) =
|
||||
let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
|
||||
activeConn = toMaybeConnection vr connRow
|
||||
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
|
||||
incognito = maybe False connIncognito activeConn
|
||||
mergedPreferences = contactUserPreferences user userPreferences preferences incognito
|
||||
connLinkToConnect = toACreatedConnLink_ connFullLink connShortLink
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, connLinkToConnect, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
|
||||
in Contact {contactId, localDisplayName, profile, activeConn, viaGroup, contactUsed, contactStatus, chatSettings, userPreferences, mergedPreferences, createdAt, updatedAt, chatTs, connLinkToConnect, contactRequestId, contactGroupMemberId, contactGrpInvSent, chatTags, chatItemTTL, uiThemes, chatDeleted, customData}
|
||||
|
||||
toACreatedConnLink_ :: Maybe AConnectionRequestUri -> Maybe AConnShortLink -> Maybe ACreatedConnLink
|
||||
toACreatedConnLink_ connFullLink connShortLink = case (connFullLink, connShortLink) of
|
||||
|
||||
@@ -211,7 +211,7 @@ chatEventNotification t@ChatTerminal {sendNotification} cc = \case
|
||||
when (groupNtf u g False) $ sendNtf ("#" <> viewGroupName g, "member " <> viewMemberName m <> " is pending review")
|
||||
CEvtConnectedToGroupMember u g m _ ->
|
||||
when (groupNtf u g False) $ sendNtf ("#" <> viewGroupName g, "member " <> viewMemberName m <> " is connected")
|
||||
CEvtReceivedContactRequest u UserContactRequest {localDisplayName = n} ->
|
||||
CEvtReceivedContactRequest u UserContactRequest {localDisplayName = n} _ ->
|
||||
when (userNtf u) $ sendNtf (viewName n <> ">", "wants to connect to you")
|
||||
_ -> pure ()
|
||||
where
|
||||
|
||||
@@ -189,6 +189,7 @@ data Contact = Contact
|
||||
updatedAt :: UTCTime,
|
||||
chatTs :: Maybe UTCTime,
|
||||
connLinkToConnect :: Maybe ACreatedConnLink,
|
||||
contactRequestId :: Maybe Int64,
|
||||
contactGroupMemberId :: Maybe GroupMemberId,
|
||||
contactGrpInvSent :: Bool,
|
||||
chatTags :: [ChatTagId],
|
||||
@@ -366,7 +367,10 @@ instance ToJSON ConnReqUriHash where
|
||||
toJSON = strToJSON
|
||||
toEncoding = strToJEncoding
|
||||
|
||||
data ChatOrRequest = CORContact Contact | CORGroup GroupInfo | CORRequest UserContactRequest
|
||||
data ChatOrRequest
|
||||
= CORContact Contact
|
||||
-- Contact is Maybe for backward compatibility with legacy requests, all new requests are created with contact
|
||||
| CORRequest UserContactRequest (Maybe Contact)
|
||||
|
||||
type UserName = Text
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
|
||||
CRContactsList u cs -> ttyUser u $ viewContactsList cs
|
||||
CRUserContactLink u UserContactLink {connLinkContact, autoAccept} -> ttyUser u $ connReqContact_ "Your chat address:" connLinkContact <> autoAcceptStatus_ autoAccept
|
||||
CRUserContactLinkUpdated u UserContactLink {autoAccept} -> ttyUser u $ autoAcceptStatus_ autoAccept
|
||||
CRContactRequestRejected u UserContactRequest {localDisplayName = c} -> ttyUser u [ttyContact c <> ": contact request rejected"]
|
||||
CRContactRequestRejected u UserContactRequest {localDisplayName = c} _ct_ -> ttyUser u [ttyContact c <> ": contact request rejected"]
|
||||
CRGroupCreated u g -> ttyUser u $ viewGroupCreated g testView
|
||||
CRGroupMembers u g -> ttyUser u $ viewGroupMembers g
|
||||
CRMemberSupportChats u g ms -> ttyUser u $ viewMemberSupportChats g ms
|
||||
@@ -416,7 +416,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView}
|
||||
CEvtContactUpdated {user = u, fromContact = c, toContact = c'} -> ttyUser u $ viewContactUpdated c c' <> viewContactPrefsUpdated u c c'
|
||||
CEvtGroupMemberUpdated {} -> []
|
||||
CEvtContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct'
|
||||
CEvtReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile
|
||||
CEvtReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} _ct_ -> ttyUser u $ viewReceivedContactRequest c profile
|
||||
CEvtRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" ci
|
||||
CEvtRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" ci
|
||||
CEvtRcvStandaloneFileComplete u _ ft -> ttyUser u $ receivingFileStandalone "completed" ft
|
||||
@@ -1826,7 +1826,7 @@ viewConnectionUserChanged User {localDisplayName = n} PendingContactConnection {
|
||||
cReqStr = strEncode $ simplexChatInvitation cReq
|
||||
|
||||
viewConnectionPlan :: ChatConfig -> ACreatedConnLink -> ConnectionPlan -> [StyledString]
|
||||
viewConnectionPlan ChatConfig {logLevel, testView} connLink = \case
|
||||
viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
|
||||
CPInvitationLink ilp -> case ilp of
|
||||
ILPOk contactSLinkData -> [invLink "ok to connect"] <> [viewJSON contactSLinkData | testView]
|
||||
ILPOwnLink -> [invLink "own link"]
|
||||
|
||||
@@ -200,14 +200,14 @@ testPaginationAllChatTypes =
|
||||
|
||||
ts7 <- iso8601Show <$> getCurrentTime
|
||||
|
||||
getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("<@cath", ""), ("@bob", "hey")]
|
||||
getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("@cath", ""), ("@bob", "hey")]
|
||||
getChats_ alice "count=3" [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on")]
|
||||
getChats_ alice ("after=" <> ts2 <> " count=2") [(":3", ""), ("<@cath", "")]
|
||||
getChats_ alice ("after=" <> ts2 <> " count=2") [(":3", ""), ("@cath", "")]
|
||||
getChats_ alice ("before=" <> ts5 <> " count=2") [("#team", "Recent history: on"), (":3", "")]
|
||||
getChats_ alice ("after=" <> ts3 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", "")]
|
||||
getChats_ alice ("before=" <> ts4 <> " count=10") [(":3", ""), ("<@cath", ""), ("@bob", "hey")]
|
||||
getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("<@cath", ""), ("@bob", "hey")]
|
||||
getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("<@cath", ""), ("@bob", "hey")]
|
||||
getChats_ alice ("before=" <> ts4 <> " count=10") [(":3", ""), ("@cath", ""), ("@bob", "hey")]
|
||||
getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("@cath", ""), ("@bob", "hey")]
|
||||
getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("@cath", ""), ("@bob", "hey")]
|
||||
getChats_ alice ("after=" <> ts7 <> " count=10") []
|
||||
getChats_ alice ("before=" <> ts1 <> " count=10") []
|
||||
|
||||
@@ -227,8 +227,6 @@ testPaginationAllChatTypes =
|
||||
|
||||
let queryUnread = "{\"type\": \"filters\", \"favorite\": false, \"unread\": true}"
|
||||
|
||||
getChats_ alice queryUnread [("<@cath", "")]
|
||||
getChats_ alice queryUnread []
|
||||
getChats_ alice ("before=" <> ts2 <> " count=10 " <> queryUnread) []
|
||||
getChats_ alice ("before=" <> ts3 <> " count=10 " <> queryUnread) [("<@cath", "")]
|
||||
getChats_ alice ("after=" <> ts2 <> " count=10 " <> queryUnread) [("<@cath", "")]
|
||||
getChats_ alice ("after=" <> ts3 <> " count=10 " <> queryUnread) []
|
||||
getChats_ alice ("after=" <> ts2 <> " count=10 " <> queryUnread) []
|
||||
|
||||
@@ -1762,7 +1762,7 @@ testMultipleUserAddresses =
|
||||
cLinkAlice <- getContactLink alice True
|
||||
bob ##> ("/c " <> cLinkAlice)
|
||||
alice <#? bob
|
||||
alice @@@ [("<@bob", "")]
|
||||
alice @@@ [("@bob", "")]
|
||||
alice ##> "/ac bob"
|
||||
alice <## "bob (Bob): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
@@ -1780,7 +1780,7 @@ testMultipleUserAddresses =
|
||||
cLinkAlisa <- getContactLink alice True
|
||||
bob ##> ("/c " <> cLinkAlisa)
|
||||
alice <#? bob
|
||||
alice #$> ("/_get chats 2 pcc=on", chats, [("<@bob", ""), ("@SimpleX Chat team", ""), ("@SimpleX-Status", ""), ("*", "")])
|
||||
alice #$> ("/_get chats 2 pcc=on", chats, [("@bob", ""), ("@SimpleX Chat team", ""), ("@SimpleX-Status", ""), ("*", "")])
|
||||
alice ##> "/ac bob"
|
||||
alice <## "bob (Bob): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
|
||||
+21
-16
@@ -257,7 +257,7 @@ testUserContactLink =
|
||||
cLink <- getContactLink alice True
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
alice @@@ [("<@bob", "")]
|
||||
alice @@@ [("@bob", "")]
|
||||
alice ##> "/ac bob"
|
||||
alice <## "bob (Bob): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
@@ -269,7 +269,7 @@ testUserContactLink =
|
||||
|
||||
cath ##> ("/c " <> cLink)
|
||||
alice <#? cath
|
||||
alice @@@ [("<@cath", ""), ("@bob", "hey")]
|
||||
alice @@@ [("@cath", ""), ("@bob", "hey")]
|
||||
alice ##> "/ac cath"
|
||||
alice <## "cath (Catherine): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
@@ -432,7 +432,7 @@ testUserContactLinkAutoAccept =
|
||||
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
alice @@@ [("<@bob", "")]
|
||||
alice @@@ [("@bob", "")]
|
||||
alice ##> "/ac bob"
|
||||
alice <## "bob (Bob): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
@@ -461,7 +461,7 @@ testUserContactLinkAutoAccept =
|
||||
|
||||
dan ##> ("/c " <> cLink)
|
||||
alice <#? dan
|
||||
alice @@@ [("<@dan", ""), ("@cath", "hey"), ("@bob", "hey")]
|
||||
alice @@@ [("@dan", ""), ("@cath", "hey"), ("@bob", "hey")]
|
||||
alice ##> "/ac dan"
|
||||
alice <## "dan (Daniel): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
@@ -479,14 +479,14 @@ testDeduplicateContactRequests = testChat3 aliceProfile bobProfile cathProfile $
|
||||
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
alice @@@ [("<@bob", "")]
|
||||
alice @@@ [("@bob", "")]
|
||||
bob @@@! [(":1", "", Just ConnJoined)]
|
||||
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
alice @@@ [("<@bob", "")]
|
||||
alice @@@ [("@bob", "")]
|
||||
bob @@@! [(":3", "", Just ConnJoined), (":2", "", Just ConnJoined), (":1", "", Just ConnJoined)]
|
||||
|
||||
alice ##> "/ac bob"
|
||||
@@ -520,7 +520,7 @@ testDeduplicateContactRequests = testChat3 aliceProfile bobProfile cathProfile $
|
||||
|
||||
cath ##> ("/c " <> cLink)
|
||||
alice <#? cath
|
||||
alice @@@ [("<@cath", ""), ("@bob", "hey")]
|
||||
alice @@@ [("@cath", ""), ("@bob", "hey")]
|
||||
alice ##> "/ac cath"
|
||||
alice <## "cath (Catherine): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
@@ -538,7 +538,7 @@ testDeduplicateContactRequestsProfileChange = testChat3 aliceProfile bobProfile
|
||||
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
alice @@@ [("<@bob", "")]
|
||||
alice @@@ [("@bob", "")]
|
||||
|
||||
bob ##> "/p bob"
|
||||
bob <## "user full name removed (your 0 contacts are notified)"
|
||||
@@ -547,19 +547,19 @@ testDeduplicateContactRequestsProfileChange = testChat3 aliceProfile bobProfile
|
||||
alice <## "bob wants to connect to you!"
|
||||
alice <## "to accept: /ac bob"
|
||||
alice <## "to reject: /rc bob (the sender will NOT be notified)"
|
||||
alice @@@ [("<@bob", "")]
|
||||
alice @@@ [("@bob", "")]
|
||||
|
||||
bob ##> "/p bob Bob Ross"
|
||||
bob <## "user full name changed to Bob Ross (your 0 contacts are notified)"
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
alice @@@ [("<@bob", "")]
|
||||
alice @@@ [("@bob", "")]
|
||||
|
||||
bob ##> "/p robert Robert"
|
||||
bob <## "user profile is changed to robert (Robert) (your 0 contacts are notified)"
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
alice @@@ [("<@robert", "")]
|
||||
alice @@@ [("@robert", "")]
|
||||
|
||||
alice ##> "/ac bob"
|
||||
alice <## "no contact request from bob"
|
||||
@@ -597,7 +597,7 @@ testDeduplicateContactRequestsProfileChange = testChat3 aliceProfile bobProfile
|
||||
|
||||
cath ##> ("/c " <> cLink)
|
||||
alice <#? cath
|
||||
alice @@@ [("<@cath", ""), ("@robert", "hey")]
|
||||
alice @@@ [("@cath", ""), ("@robert", "hey")]
|
||||
alice ##> "/ac cath"
|
||||
alice <## "cath (Catherine): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
@@ -614,8 +614,11 @@ testRejectContactAndDeleteUserContact = testChat3 aliceProfile bobProfile cathPr
|
||||
cLink <- getContactLink alice True
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
alice @@@ [("@bob", "")]
|
||||
|
||||
alice ##> "/rc bob"
|
||||
alice <## "bob: contact request rejected"
|
||||
alice @@@ []
|
||||
(bob </)
|
||||
|
||||
alice ##> "/_show_address 1"
|
||||
@@ -898,7 +901,7 @@ testPlanAddressOkKnown =
|
||||
|
||||
bob ##> ("/c " <> cLink)
|
||||
alice <#? bob
|
||||
alice @@@ [("<@bob", "")]
|
||||
alice @@@ [("@bob", "")]
|
||||
alice ##> "/ac bob"
|
||||
alice <## "bob (Bob): accepting contact request, you can send messages to contact"
|
||||
concurrently_
|
||||
@@ -937,7 +940,7 @@ testPlanAddressOwn ps =
|
||||
alice <## "alice_1 (Alice) wants to connect to you!"
|
||||
alice <## "to accept: /ac alice_1"
|
||||
alice <## "to reject: /rc alice_1 (the sender will NOT be notified)"
|
||||
alice @@@ [("<@alice_1", ""), (":2", "")]
|
||||
alice @@@ [("@alice_1", ""), (":2", "")]
|
||||
alice ##> "/ac alice_1"
|
||||
alice <## "alice_1 (Alice): accepting contact request, you can send messages to contact"
|
||||
alice
|
||||
@@ -2802,8 +2805,10 @@ testShortLinkAddressPrepareContact =
|
||||
<### [ "alice: connection started",
|
||||
WithTime "@alice hello"
|
||||
]
|
||||
-- TODO [short links] for alice create message from contact request
|
||||
alice <## "bob (Bob) wants to connect to you!"
|
||||
alice
|
||||
<### [ "bob (Bob) wants to connect to you!",
|
||||
WithTime "bob> hello"
|
||||
]
|
||||
alice <## "to accept: /ac bob"
|
||||
alice <## "to reject: /rc bob (the sender will NOT be notified)"
|
||||
alice ##> "/ac bob"
|
||||
|
||||
Reference in New Issue
Block a user