This commit is contained in:
Woit 2024-11-22 17:34:56 +01:00
parent 4c42b21559
commit 49f1028d69
7 changed files with 110 additions and 122 deletions

View file

@ -11,11 +11,11 @@ import UniformTypeIdentifiers
struct AddContactMenu: View { struct AddContactMenu: View {
var delegate: SheetDismisserProtocol var delegate: SheetDismisserProtocol
static private let jidFaultyPattern = "^([^@]+@)?.+(\\..{2,})?$" private static let jidFaultyPattern = "^([^@]+@)?.+(\\..{2,})?$"
@State private var enabledAccounts: [xmpp] @State private var enabledAccounts: [xmpp]
@State private var selectedAccount: Int @State private var selectedAccount: Int
@State private var scannedFingerprints: [NSNumber:Data]? = nil @State private var scannedFingerprints: [NSNumber: Data]? = nil
@State private var importScannedFingerprints: Bool = false @State private var importScannedFingerprints: Bool = false
@State private var toAdd: String = "" @State private var toAdd: String = ""
@ -23,43 +23,43 @@ struct AddContactMenu: View {
@State private var showAlert = false @State private var showAlert = false
// note: dismissLabel is not accessed but defined at the .alert() section // note: dismissLabel is not accessed but defined at the .alert() section
@State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close")) @State private var alertPrompt = AlertPrompt(dismissLabel: Text("Close"))
@State private var invitationResult: [String:AnyObject]? = nil @State private var invitationResult: [String: AnyObject]? = nil
@StateObject private var overlay = LoadingOverlayState() @StateObject private var overlay = LoadingOverlayState()
@State private var showQRCodeScanner = false @State private var showQRCodeScanner = false
@State private var success = false @State private var success = false
@State private var newContact : MLContact? @State private var newContact: MLContact?
@State private var isEditingJid = false @State private var isEditingJid = false
private let dismissWithNewContact: (MLContact) -> () private let dismissWithNewContact: (MLContact) -> Void
private let preauthToken: String? private let preauthToken: String?
init(delegate: SheetDismisserProtocol, dismissWithNewContact: @escaping (MLContact) -> (), prefillJid: String = "", preauthToken:String? = nil, prefillAccount:xmpp? = nil, omemoFingerprints: [NSNumber:Data]? = nil) { init(delegate: SheetDismisserProtocol, dismissWithNewContact: @escaping (MLContact) -> Void, prefillJid: String = "", preauthToken: String? = nil, prefillAccount: xmpp? = nil, omemoFingerprints: [NSNumber: Data]? = nil) {
self.delegate = delegate self.delegate = delegate
self.dismissWithNewContact = dismissWithNewContact self.dismissWithNewContact = dismissWithNewContact
//self.toAdd = State(wrappedValue: prefillJid) // self.toAdd = State(wrappedValue: prefillJid)
self.toAdd = prefillJid toAdd = prefillJid
self.preauthToken = preauthToken self.preauthToken = preauthToken
//only display omemo ui part if there are any fingerprints (the checks below test for nil, not for 0) // only display omemo ui part if there are any fingerprints (the checks below test for nil, not for 0)
if omemoFingerprints?.count ?? 0 > 0 { if omemoFingerprints?.count ?? 0 > 0 {
self.scannedFingerprints = omemoFingerprints scannedFingerprints = omemoFingerprints
} }
let enabledAccounts = MLXMPPManager.sharedInstance().connectedXMPP as! [xmpp] let enabledAccounts = MLXMPPManager.sharedInstance().connectedXMPP as! [xmpp]
self.enabledAccounts = enabledAccounts self.enabledAccounts = enabledAccounts
self.selectedAccount = enabledAccounts.first != nil ? 0 : -1; selectedAccount = enabledAccounts.first != nil ? 0 : -1
if let prefillAccount = prefillAccount { if let prefillAccount = prefillAccount {
for index in enabledAccounts.indices { for index in enabledAccounts.indices {
if enabledAccounts[index].accountID.isEqual(to:prefillAccount.accountID) { if enabledAccounts[index].accountID.isEqual(to: prefillAccount.accountID) {
self.selectedAccount = index selectedAccount = index
} }
} }
} }
} }
// FIXME duplicate code from WelcomeLogIn.swift, maybe move to SwiftuiHelpers // FIXME: duplicate code from WelcomeLogIn.swift, maybe move to SwiftuiHelpers
private var toAddEmptyAlert: Bool { private var toAddEmptyAlert: Bool {
alertPrompt.title = Text("No Empty Values!") alertPrompt.title = Text("No Empty Values!")
alertPrompt.message = Text("Please make sure you have entered a valid jid.") alertPrompt.message = Text("Please make sure you have entered a valid jid.")
@ -71,88 +71,88 @@ struct AddContactMenu: View {
alertPrompt.message = Text("The jid you want to add should be in in the format user@domain.tld.") alertPrompt.message = Text("The jid you want to add should be in in the format user@domain.tld.")
return toAddInvalid return toAddInvalid
} }
private func errorAlert(title: Text, message: Text = Text("")) { private func errorAlert(title: Text, message: Text = Text("")) {
alertPrompt.title = title alertPrompt.title = title
alertPrompt.message = message alertPrompt.message = message
showAlert = true showAlert = true
} }
private func successAlert(title: Text, message: Text) { private func successAlert(title: Text, message: Text) {
alertPrompt.title = title alertPrompt.title = title
alertPrompt.message = message alertPrompt.message = message
self.success = true // < dismiss entire view on close success = true // < dismiss entire view on close
showAlert = true showAlert = true
} }
private var toAddEmpty: Bool { private var toAddEmpty: Bool {
return toAdd.isEmpty toAdd.isEmpty
}
private var toAddInvalid: Bool {
return toAdd.range(of: AddContactMenu.jidFaultyPattern, options:.regularExpression) == nil
} }
func trustFingerprints(_ fingerprints:[NSNumber:Data]?, for jid:String, on account:xmpp) { private var toAddInvalid: Bool {
//we don't untrust other devices not included in here, because conversations only exports its own fingerprint toAdd.range(of: AddContactMenu.jidFaultyPattern, options: .regularExpression) == nil
}
func trustFingerprints(_ fingerprints: [NSNumber: Data]?, for jid: String, on account: xmpp) {
// we don't untrust other devices not included in here, because conversations only exports its own fingerprint
if let fingerprints = fingerprints { if let fingerprints = fingerprints {
for (deviceId, fingerprint) in fingerprints { for (deviceId, fingerprint) in fingerprints {
let address = SignalAddress.init(name:jid, deviceId:deviceId.int32Value) let address = SignalAddress(name: jid, deviceId: deviceId.int32Value)
let knownDevices = Array(account.omemo.knownDevices(forAddressName:jid)) let knownDevices = Array(account.omemo.knownDevices(forAddressName: jid))
if !knownDevices.contains(deviceId) { if !knownDevices.contains(deviceId) {
account.omemo.addIdentityManually(address, identityKey:fingerprint) account.omemo.addIdentityManually(address, identityKey: fingerprint)
assert(account.omemo.getIdentityFor(address) == fingerprint, "The stored and created fingerprint should match") assert(account.omemo.getIdentityFor(address) == fingerprint, "The stored and created fingerprint should match")
} }
//trust device/fingerprint if fingerprints match // trust device/fingerprint if fingerprints match
let knownFingerprintHex = HelperTools.signalHexKey(with:account.omemo.getIdentityFor(address)) let knownFingerprintHex = HelperTools.signalHexKey(with: account.omemo.getIdentityFor(address))
let addedFingerprintHex = HelperTools.signalHexKey(with:fingerprint) let addedFingerprintHex = HelperTools.signalHexKey(with: fingerprint)
if knownFingerprintHex.uppercased() == addedFingerprintHex.uppercased() { if knownFingerprintHex.uppercased() == addedFingerprintHex.uppercased() {
account.omemo.updateTrust(true, for:address) account.omemo.updateTrust(true, for: address)
} }
} }
} }
} }
func addJid(jid: String) { func addJid(jid: String) {
let account = self.enabledAccounts[selectedAccount] let account = enabledAccounts[selectedAccount]
let contact = MLContact.createContact(fromJid: jid, andAccountID: account.accountID) let contact = MLContact.createContact(fromJid: jid, andAccountID: account.accountID)
if contact.isInRoster { if contact.isInRoster {
self.newContact = contact newContact = contact
//import omemo fingerprints as manually trusted, if requested // import omemo fingerprints as manually trusted, if requested
trustFingerprints(self.importScannedFingerprints ? self.scannedFingerprints : [:], for:jid, on:account) trustFingerprints(importScannedFingerprints ? scannedFingerprints : [:], for: jid, on: account)
//only alert of already known contact if we did not import the omemo fingerprints // only alert of already known contact if we did not import the omemo fingerprints
if !self.importScannedFingerprints || self.scannedFingerprints?.count ?? 0 == 0 { if !importScannedFingerprints || scannedFingerprints?.count ?? 0 == 0 {
if self.enabledAccounts.count > 1 { if enabledAccounts.count > 1 {
self.success = true success = true
successAlert(title: Text("Already present"), message: Text("This contact is already in the contact list of the selected account")) successAlert(title: Text("Already present"), message: Text("This contact is already in the contact list of the selected account"))
} else { } else {
self.success = true success = true
successAlert(title: Text("Already present"), message: Text("This contact is already in your contact list")) successAlert(title: Text("Already present"), message: Text("This contact is already in your contact list"))
} }
} }
return return
} }
showPromisingLoadingOverlay(overlay, headline:NSLocalizedString("Adding...", comment: ""), description:"") { showPromisingLoadingOverlay(overlay, headline: NSLocalizedString("Adding...", comment: ""), description: "") {
account.checkJidType(jid) account.checkJidType(jid)
}.done { type in }.done { type in
let type = type as! String let type = type as! String
if type == "account" { if type == "account" {
let contact = MLContact.createContact(fromJid: jid, andAccountID: account.accountID) let contact = MLContact.createContact(fromJid: jid, andAccountID: account.accountID)
self.newContact = contact self.newContact = contact
MLXMPPManager.sharedInstance().add(contact, withPreauthToken:preauthToken) MLXMPPManager.sharedInstance().add(contact, withPreauthToken: preauthToken)
//import omemo fingerprints as manually trusted, if requested // import omemo fingerprints as manually trusted, if requested
trustFingerprints(self.importScannedFingerprints ? self.scannedFingerprints : [:], for:jid, on:account) trustFingerprints(self.importScannedFingerprints ? self.scannedFingerprints : [:], for: jid, on: account)
successAlert(title: Text("Permission Requested"), message: Text("The new contact will be added to your contacts list when the person you've added has approved your request.")) successAlert(title: Text("Permission Requested"), message: Text("The new contact will be added to your contacts list when the person you've added has approved your request."))
} else if type == "muc" { } else if type == "muc" {
showPromisingLoadingOverlay(overlay, headlineView:Text("Adding Group/Channel..."), descriptionView:Text("")) { showPromisingLoadingOverlay(overlay, headlineView: Text("Adding Group/Channel..."), descriptionView: Text("")) {
promisifyMucAction(account:account, mucJid:jid) { promisifyMucAction(account: account, mucJid: jid) {
account.joinMuc(jid) account.joinMuc(jid)
} }
}.done { _ in }.done { _ in
self.newContact = MLContact.createContact(fromJid: jid, andAccountID: account.accountID) self.newContact = MLContact.createContact(fromJid: jid, andAccountID: account.accountID)
successAlert(title: Text("Success!"), message: Text("Successfully joined group/channel \(jid)!")) successAlert(title: Text("Success!"), message: Text("Successfully joined group/channel \(jid)!"))
}.catch { error in }.catch { error in
errorAlert(title: Text("Error entering group/channel!"), message: Text("\(String(describing:error))")) errorAlert(title: Text("Error entering group/channel!"), message: Text("\(String(describing: error))"))
} }
} }
}.catch { error in }.catch { error in
@ -161,20 +161,18 @@ struct AddContactMenu: View {
} }
var body: some View { var body: some View {
let account = self.enabledAccounts[selectedAccount] let account = enabledAccounts[selectedAccount]
let splitJid = HelperTools.splitJid(account.connectionProperties.identity.jid) let splitJid = HelperTools.splitJid(account.connectionProperties.identity.jid)
Form { Form {
if enabledAccounts.isEmpty { if enabledAccounts.isEmpty {
Text("Please make sure at least one account has connected before trying to add a contact or channel.") Text("Please make sure at least one account has connected before trying to add a contact or channel.")
.foregroundColor(.secondary) .foregroundColor(.secondary)
} } else {
else if !DataLayer.sharedInstance().allContactRequests().isEmpty {
{
if DataLayer.sharedInstance().allContactRequests().count > 0 {
ContactRequestsMenu() ContactRequestsMenu()
} }
Section(header:Text("Contact and Group/Channel Jids are usually in the format: name@domain.tld")) { Section(header: Text("Contact and Group/Channel Jids are usually in the format: name@domain.tld")) {
if enabledAccounts.count > 1 { if enabledAccounts.count > 1 {
Picker("Use account", selection: $selectedAccount) { Picker("Use account", selection: $selectedAccount) {
ForEach(Array(self.enabledAccounts.enumerated()), id: \.element) { idx, account in ForEach(Array(self.enabledAccounts.enumerated()), id: \.element) { idx, account in
@ -189,19 +187,19 @@ struct AddContactMenu: View {
.autocapitalization(.none) .autocapitalization(.none)
.autocorrectionDisabled() .autocorrectionDisabled()
.keyboardType(.emailAddress) .keyboardType(.emailAddress)
.addClearButton(isEditing: isEditingJid, text:$toAdd) .addClearButton(isEditing: isEditingJid, text: $toAdd)
.disabled(scannedFingerprints != nil) .disabled(scannedFingerprints != nil)
.foregroundColor(scannedFingerprints != nil ? .secondary : .primary) .foregroundColor(scannedFingerprints != nil ? .secondary : .primary)
.onChange(of: toAdd) { _ in toAdd = toAdd.replacingOccurrences(of: " ", with: "") } .onChange(of: toAdd) { _ in toAdd = toAdd.replacingOccurrences(of: " ", with: "") }
if scannedFingerprints != nil && scannedFingerprints!.count > 0 { if scannedFingerprints != nil && !scannedFingerprints!.isEmpty {
Section(header: Text("A contact was scanned through the QR code scanner")) { Section(header: Text("A contact was scanned through the QR code scanner")) {
Toggle(isOn: $importScannedFingerprints) { Toggle(isOn: $importScannedFingerprints) {
Text("Import and trust OMEMO fingerprints from QR code") Text("Import and trust OMEMO fingerprints from QR code")
} }
} }
} }
if scannedFingerprints != nil { if scannedFingerprints != nil {
Button(action: { Button(action: {
toAdd = "" toAdd = ""
@ -212,10 +210,10 @@ struct AddContactMenu: View {
.foregroundColor(.red) .foregroundColor(.red)
}) })
} }
HStack { HStack {
Spacer() Spacer()
Button(action: { Button(action: {
showAlert = toAddEmptyAlert || toAddInvalidAlert showAlert = toAddEmptyAlert || toAddInvalidAlert
@ -236,8 +234,8 @@ struct AddContactMenu: View {
.buttonStyle(MonalProminentButtonStyle()) .buttonStyle(MonalProminentButtonStyle())
} }
} }
if DataLayer.sharedInstance().allContactRequests().count == 0 { if DataLayer.sharedInstance().allContactRequests().isEmpty {
Section { Section {
ContactRequestsMenu() ContactRequestsMenu()
} }
@ -246,7 +244,7 @@ struct AddContactMenu: View {
} }
.padding() .padding()
.alert(isPresented: $showAlert) { .alert(isPresented: $showAlert) {
Alert(title: alertPrompt.title, message: alertPrompt.message, dismissButton:.default(Text("Close"), action: { Alert(title: alertPrompt.title, message: alertPrompt.message, dismissButton: .default(Text("Close"), action: {
showAlert = false showAlert = false
if self.success == true { if self.success == true {
if self.newContact != nil { if self.newContact != nil {
@ -257,7 +255,7 @@ struct AddContactMenu: View {
} }
})) }))
} }
.richAlert(isPresented: $invitationResult, title:Text("Invitation for \(splitJid["host"]!) created")) { data in .richAlert(isPresented: $invitationResult, title: Text("Invitation for \(splitJid["host"]!) created")) { data in
VStack { VStack {
Image(uiImage: createQrCode(value: data["landing"] as! String)) Image(uiImage: createQrCode(value: data["landing"] as! String))
.interpolation(.none) .interpolation(.none)
@ -266,15 +264,15 @@ struct AddContactMenu: View {
.aspectRatio(1, contentMode: .fit) .aspectRatio(1, contentMode: .fit)
if let expires = data["expires"] as? Date { if let expires = data["expires"] as? Date {
Text("This invitation will expire on \(expires.formatted(date:.numeric, time:.shortened))") Text("This invitation will expire on \(expires.formatted(date: .numeric, time: .shortened))")
.font(.footnote) .font(.footnote)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
} }
} buttons: { data in } buttons: { data in
Button(action: { Button(action: {
UIPasteboard.general.setValue(data["landing"] as! String, forPasteboardType:UTType.utf8PlainText.identifier as String) UIPasteboard.general.setValue(data["landing"] as! String, forPasteboardType: UTType.utf8PlainText.identifier as String)
invitationResult = nil invitationResult = nil
}) { }) {
ShareLink("Share invitation link", item: URL(string: data["landing"] as! String)!) ShareLink("Share invitation link", item: URL(string: data["landing"] as! String)!)
@ -309,17 +307,17 @@ struct AddContactMenu: View {
ToolbarItemGroup(placement: .navigationBarTrailing) { ToolbarItemGroup(placement: .navigationBarTrailing) {
if account.connectionProperties.discoveredAdhocCommands["urn:xmpp:invite#invite"] != nil { if account.connectionProperties.discoveredAdhocCommands["urn:xmpp:invite#invite"] != nil {
Button(action: { Button(action: {
DDLogVerbose("Trying to create invitation for: \(String(describing:splitJid["host"]!))") DDLogVerbose("Trying to create invitation for: \(String(describing: splitJid["host"]!))")
showLoadingOverlay(overlay, headline: NSLocalizedString("Creating invitation...", comment: "")) showLoadingOverlay(overlay, headline: NSLocalizedString("Creating invitation...", comment: ""))
account.createInvitation(completion: { account.createInvitation(completion: {
let result = $0 as! [String:AnyObject] let result = $0 as! [String: AnyObject]
DispatchQueue.main.async { DispatchQueue.main.async {
hideLoadingOverlay(overlay) hideLoadingOverlay(overlay)
DDLogVerbose("Got invitation result: \(String(describing:result))") DDLogVerbose("Got invitation result: \(String(describing: result))")
if result["success"] as! Bool == true { if result["success"] as! Bool == true {
invitationResult = result invitationResult = result
} else { } else {
errorAlert(title:Text("Failed to create invitation for \(splitJid["host"]!)"), message:Text(result["error"] as! String)) errorAlert(title: Text("Failed to create invitation for \(splitJid["host"]!)"), message: Text(result["error"] as! String))
} }
} }
}) })
@ -341,7 +339,7 @@ struct AddContactMenu: View {
struct AddContactMenu_Previews: PreviewProvider { struct AddContactMenu_Previews: PreviewProvider {
static var delegate = SheetDismisserProtocol() static var delegate = SheetDismisserProtocol()
static var previews: some View { static var previews: some View {
AddContactMenu(delegate: delegate, dismissWithNewContact: { c in AddContactMenu(delegate: delegate, dismissWithNewContact: { _ in
}) })
} }
} }

View file

@ -11,12 +11,12 @@ import SwiftUI
struct ContactViewEntry: View { struct ContactViewEntry: View {
private let contact: MLContact private let contact: MLContact
@Binding private var selectedContactForContactDetails: ObservableKVOWrapper<MLContact>? @Binding private var selectedContactForContactDetails: ObservableKVOWrapper<MLContact>?
private let dismissWithContact: (MLContact) -> () private let dismissWithContact: (MLContact) -> Void
@State private var shouldPresentRemoveContactAlert: Bool = false @State private var shouldPresentRemoveContactAlert: Bool = false
private var removeContactButtonText: String { private var removeContactButtonText: String {
if (!isDeletable) { if !isDeletable {
return "Cannot delete notes to self" return "Cannot delete notes to self"
} }
return contact.isMuc ? "Remove Conversation" : "Remove Contact" return contact.isMuc ? "Remove Conversation" : "Remove Contact"
@ -34,9 +34,9 @@ struct ContactViewEntry: View {
!contact.isSelfChat !contact.isSelfChat
} }
init (contact: MLContact, selectedContactForContactDetails: Binding<ObservableKVOWrapper<MLContact>?>, dismissWithContact: @escaping (MLContact) -> ()) { init(contact: MLContact, selectedContactForContactDetails: Binding<ObservableKVOWrapper<MLContact>?>, dismissWithContact: @escaping (MLContact) -> Void) {
self.contact = contact self.contact = contact
self._selectedContactForContactDetails = selectedContactForContactDetails _selectedContactForContactDetails = selectedContactForContactDetails
self.dismissWithContact = dismissWithContact self.dismissWithContact = dismissWithContact
} }
@ -88,26 +88,26 @@ struct ContactViewEntry: View {
struct ContactsView: View { struct ContactsView: View {
@ObservedObject private var contacts: Contacts @ObservedObject private var contacts: Contacts
private let delegate: SheetDismisserProtocol private let delegate: SheetDismisserProtocol
private let dismissWithContact: (MLContact) -> () private let dismissWithContact: (MLContact) -> Void
@State private var searchText: String = "" @State private var searchText: String = ""
@State private var selectedContactForContactDetails: ObservableKVOWrapper<MLContact>? = nil @State private var selectedContactForContactDetails: ObservableKVOWrapper<MLContact>? = nil
init(contacts: Contacts, delegate: SheetDismisserProtocol, dismissWithContact: @escaping (MLContact) -> ()) { init(contacts: Contacts, delegate: SheetDismisserProtocol, dismissWithContact: @escaping (MLContact) -> Void) {
self.contacts = contacts self.contacts = contacts
self.delegate = delegate self.delegate = delegate
self.dismissWithContact = dismissWithContact self.dismissWithContact = dismissWithContact
} }
private static func shouldDisplayContact(contact: MLContact) -> Bool { private static func shouldDisplayContact(contact: MLContact) -> Bool {
#if IS_QUICKSY #if IS_QUICKSY
return true return true
#endif #endif
return contact.isSubscribedTo || contact.hasOutgoingContactRequest || contact.isSubscribedFrom return contact.isSubscribedTo || contact.hasOutgoingContactRequest || contact.isSubscribedFrom
} }
private var contactList: [MLContact] { private var contactList: [MLContact] {
return contacts.contacts contacts.contacts
.filter(ContactsView.shouldDisplayContact) .filter(ContactsView.shouldDisplayContact)
.sorted { ContactsView.sortingCriteria($0) < ContactsView.sortingCriteria($1) } .sorted { ContactsView.sortingCriteria($0) < ContactsView.sortingCriteria($1) }
} }
@ -118,7 +118,7 @@ struct ContactsView: View {
} }
private static func sortingCriteria(_ contact: MLContact) -> (String, String) { private static func sortingCriteria(_ contact: MLContact) -> (String, String) {
return (contact.contactDisplayName.lowercased(), contact.contactJid.lowercased()) (contact.contactDisplayName.lowercased(), contact.contactJid.lowercased())
} }
private func searchMatchesContact(contact: MLContact, search: String) -> Bool { private func searchMatchesContact(contact: MLContact, search: String) -> Bool {
@ -164,7 +164,7 @@ struct ContactsView: View {
} }
} }
.sheet(item: $selectedContactForContactDetails) { selectedContact in .sheet(item: $selectedContactForContactDetails) { selectedContact in
AnyView(AddTopLevelNavigation(withDelegate: delegate, to: ContactDetails(delegate:delegate, contact:selectedContact))) AnyView(AddTopLevelNavigation(withDelegate: delegate, to: ContactDetails(delegate: delegate, contact: selectedContact)))
} }
} }
} }
@ -175,20 +175,20 @@ class Contacts: ObservableObject {
private var subscriptions: Set<AnyCancellable> = Set() private var subscriptions: Set<AnyCancellable> = Set()
init() { init() {
self.contacts = Set(DataLayer.sharedInstance().contactList()) contacts = Set(DataLayer.sharedInstance().contactList())
self.requestCount = DataLayer.sharedInstance().allContactRequests().count requestCount = DataLayer.sharedInstance().allContactRequests().count
subscriptions = [ subscriptions = [
NotificationCenter.default.publisher(for: NSNotification.Name("kMonalContactRemoved")) NotificationCenter.default.publisher(for: NSNotification.Name("kMonalContactRemoved"))
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink() { _ in self.refreshContacts() }, .sink { _ in self.refreshContacts() },
NotificationCenter.default.publisher(for: NSNotification.Name("kMonalContactRefresh")) NotificationCenter.default.publisher(for: NSNotification.Name("kMonalContactRefresh"))
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink() { _ in self.refreshContacts() } .sink { _ in self.refreshContacts() }
] ]
} }
private func refreshContacts() { private func refreshContacts() {
self.contacts = Set(DataLayer.sharedInstance().contactList()) contacts = Set(DataLayer.sharedInstance().contactList())
self.requestCount = DataLayer.sharedInstance().allContactRequests().count requestCount = DataLayer.sharedInstance().allContactRequests().count
} }
} }

View file

@ -123,17 +123,7 @@ struct AddContactOrChannelScreen: View {
router.dismissModal() router.dismissModal()
} }
do { await wrapper.addContact(contactJid: contactJID, forAccountID: ownerAccount.id)
try await wrapper.addContact(contactJid: contactJID, forAccountID: ownerAccount.id) router.dismissScreen()
router.dismissScreen()
} catch {
router.showAlert(
.alert,
title: L10n.Global.Error.title,
subtitle: L10n.Contacts.Add.serverError
) {
Button(L10n.Global.ok, role: .cancel) {}
}
}
} }
} }

View file

@ -50,7 +50,7 @@ private struct ContactsScreenRow: View {
var body: some View { var body: some View {
SharedListRow( SharedListRow(
iconType: .charCircle(contact.name?.firstLetter ?? contact.contactJid.firstLetter), iconType: .charCircle(contact.name ?? contact.contactJid),
text: contact.contactJid, text: contact.contactJid,
controlType: .none controlType: .none
) )

View file

@ -1,4 +1,3 @@
enum AimErrors: Error { enum AimErrors: Error {
case loginError case loginError
case addContactError
} }

View file

@ -51,6 +51,6 @@ struct Contact: Identifiable {
init?(_ obj: MLContact) { init?(_ obj: MLContact) {
ownerId = obj.accountID.intValue ownerId = obj.accountID.intValue
contactJid = obj.contactJid contactJid = obj.contactJid
name = obj.nickName name = obj.nickName.isEmpty ? nil : obj.nickName
} }
} }

View file

@ -41,15 +41,13 @@ extension MonalXmppWrapper {
} }
} }
func addContact(contactJid: String, forAccountID: Int) async throws { func addContact(contactJid: String, forAccountID: Int) async {
_ = await Task { [weak self] in await withCheckedContinuation { [weak self] cnt in
let result = self?.db.addContact(contactJid, forAccount: NSNumber(value: forAccountID), nickname: nil) ?? true let contact = MLContact.createContact(fromJid: contactJid, andAccountID: NSNumber(value: forAccountID))
if result { self?.xmpp.add(contact)
NotificationCenter.default.post(name: Notification.Name(kMonalContactRefresh), object: nil) cnt.resume()
} else { }
throw AimErrors.addContactError NotificationCenter.default.post(name: Notification.Name(kMonalContactRefresh), object: nil)
}
}.result
} }
} }
@ -137,6 +135,9 @@ private extension MonalXmppWrapper {
} }
func refreshContacts() { func refreshContacts() {
contacts = db.contactList().compactMap { Contact($0) } contacts = db.contactList()
.filter { $0.isSubscribedTo || $0.hasOutgoingContactRequest || $0.isSubscribedFrom }
.filter { !$0.isSelfChat } // removed for now
.compactMap { Contact($0) }
} }
} }