diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj index 9e5a22f..2ef10ea 100644 --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -1411,7 +1411,7 @@ name = tools; sourceTree = ""; }; - 29B97314FDCFA39411CA2CEA = { + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( 8414ADF92A7ABAC900EFFCCC /* Packages */, @@ -2064,7 +2064,7 @@ eu, "es-AR", ); - mainGroup = 29B97314FDCFA39411CA2CEA; + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; packageReferences = ( C1F5C7AD2777638B0001F295 /* XCRemoteSwiftPackageReference "swift-collections" */, 841898A82957712000FEC77D /* XCRemoteSwiftPackageReference "ViewExtractor" */, diff --git a/Monal/another.im/Views/Main/Contacts/AddContactOrChannelScreen.swift b/Monal/another.im/Views/Main/Contacts/AddContactOrChannelScreen.swift index 310126b..a3d2752 100644 --- a/Monal/another.im/Views/Main/Contacts/AddContactOrChannelScreen.swift +++ b/Monal/another.im/Views/Main/Contacts/AddContactOrChannelScreen.swift @@ -11,7 +11,7 @@ struct AddContactOrChannelScreen: View { @FocusState private var focus: Field? - // @State private var ownerCredentials: Credentials? + @State private var ownerAccount: Account? @State private var contactJID: String = "" var body: some View { @@ -56,13 +56,13 @@ struct AddContactOrChannelScreen: View { .frame(alignment: .leading) Spacer() } - // UniversalInputCollection.DropDownMenu( - // prompt: "Use account", - // elements: activeClientsCredentials, - // selected: $ownerCredentials, - // focus: $focus, - // fieldType: .account - // ) + UniversalInputCollection.DropDownMenu( + prompt: "Use account", + elements: wrapper.accounts, + selected: $ownerAccount, + focus: $focus, + fieldType: .account + ) // Contact text input HStack(spacing: 0) { @@ -72,18 +72,18 @@ struct AddContactOrChannelScreen: View { .frame(alignment: .leading) Spacer() } - // UniversalInputCollection.TextField( - // prompt: "Contact or channel JID", - // text: $contactJID, - // focus: $focus, - // fieldType: .contact, - // contentType: .emailAddress, - // keyboardType: .emailAddress, - // submitLabel: .done, - // action: { - // focus = .account - // } - // ) + UniversalInputCollection.TextField( + prompt: "Contact or channel JID", + text: $contactJID, + focus: $focus, + fieldType: .contact, + contentType: .emailAddress, + keyboardType: .emailAddress, + submitLabel: .done, + action: { + focus = .account + } + ) // Save button Button { @@ -94,7 +94,7 @@ struct AddContactOrChannelScreen: View { Text(L10n.Global.save) } .buttonStyle(PrimaryButtonStyle()) - // .disabled(!inputValid) + .disabled(!inputValid) .padding(.top) Spacer() } @@ -102,24 +102,18 @@ struct AddContactOrChannelScreen: View { } } .onAppear { - // if let exists = activeClientsCredentials.first { - // ownerCredentials = exists - // } + if let exists = wrapper.accounts.first { + ownerAccount = exists + } } } - // private var activeClientsCredentials: [Credentials] { - // clientsStore.clients - // .filter { $0.state != .disabled } - // .map { $0.credentials } - // } + private var inputValid: Bool { + ownerAccount != nil && !contactJID.isEmpty && UniversalInputCollection.Validators.isEmail(contactJID) + } - // private var inputValid: Bool { - // ownerCredentials != nil && !contactJID.isEmpty && UniversalInputCollection.Validators.isEmail(contactJID) - // } - // private func save() async { - // guard let ownerCredentials = ownerCredentials else { return } + guard let ownerAccount = ownerAccount else { return } router.showModal { LoadingScreen() diff --git a/Monal/another.im/Views/Main/Contacts/ContactsScreen.swift b/Monal/another.im/Views/Main/Contacts/ContactsScreen.swift index 8908fae..8dd4c53 100644 --- a/Monal/another.im/Views/Main/Contacts/ContactsScreen.swift +++ b/Monal/another.im/Views/Main/Contacts/ContactsScreen.swift @@ -26,94 +26,68 @@ struct ContactsScreen: View { ) // Contacts list -// if !clientsStore.actualRosters.isEmpty { -// List { -// ForEach(elements.indices, id: \.self) { index in -// let element = elements[index] -// if let roster = element as? Roster { -// ContactsScreenRow( -// roster: roster -// ) -// } else if let bareJid = element as? String { -// SharedSectionTitle(text: bareJid) -// } -// } -// } -// .listStyle(.plain) -// .background(Color.Material.Background.light) -// } else { -// Spacer() -// } + if !wrapper.contacts.isEmpty { + List { + ForEach(wrapper.contacts) { + ContactsScreenRow(contact: $0) + } + } + .listStyle(.plain) + .background(Color.Material.Background.light) + } else { + Spacer() + } } } } - - private var elements: [Any] { - [] - // if clientsStore.clients.filter({ $0.credentials.isActive }).count == 1 { - // return clientsStore.actualRosters - // } else { - // var result: [Any] = [] - // for roster in clientsStore.actualRosters { - // if result.isEmpty { - // result.append(roster.bareJid) - // } else if let last = result.last as? Roster, last.bareJid != roster.bareJid { - // result.append(roster.bareJid) - // } - // result.append(roster) - // } - // return result - // } - } } private struct ContactsScreenRow: View { @EnvironmentObject var wrapper: MonalXmppWrapper @Environment(\.router) var router - // var roster: Roster + var contact: Contact var body: some View { - Text("nothing for now") - // SharedListRow( - // iconType: .charCircle(roster.name?.firstLetter ?? roster.contactBareJid.firstLetter), - // text: roster.contactBareJid, - // controlType: .none - // ) - // .onTapGesture { - // startChat() - // } - // .swipeActions(edge: .trailing, allowsFullSwipe: false) { - // Button { - // router.showAlert(.confirmationDialog, title: L10n.Contacts.deleteContact, subtitle: L10n.Contacts.Delete.message) { - // deleteConfirmation - // } - // } label: { - // Label("", systemImage: "trash") - // } - // .tint(Color.red) - // } - // .contextMenu { - // Button(L10n.Contacts.sendMessage, systemImage: "message") { - // startChat() - // } - // Divider() - // - // Button(L10n.Contacts.editContact) { - // print("Edit contact") - // } - // - // Button(L10n.Contacts.selectContact) { - // print("Select contact") - // } - // - // Divider() - // Button(L10n.Contacts.deleteContact, systemImage: "trash", role: .destructive) { - // router.showAlert(.confirmationDialog, title: L10n.Contacts.deleteContact, subtitle: L10n.Contacts.Delete.message) { - // deleteConfirmation - // } - // } - // } + SharedListRow( + iconType: .charCircle(contact.name?.firstLetter ?? contact.contactJid.firstLetter), + text: contact.contactJid, + controlType: .none + ) + .onTapGesture { + startChat() + } + .swipeActions(edge: .trailing, allowsFullSwipe: false) { + Button { + router.showAlert(.confirmationDialog, title: L10n.Contacts.deleteContact, subtitle: L10n.Contacts.Delete.message) { + deleteConfirmation + } + } label: { + Label("", systemImage: "trash") + } + .tint(Color.red) + } + .contextMenu { + Button(L10n.Contacts.sendMessage, systemImage: "message") { + startChat() + } + Divider() + + Button(L10n.Contacts.editContact) { + print("Edit contact") + } + + Button(L10n.Contacts.selectContact) { + print("Select contact") + } + + Divider() + Button(L10n.Contacts.deleteContact, systemImage: "trash", role: .destructive) { + router.showAlert(.confirmationDialog, title: L10n.Contacts.deleteContact, subtitle: L10n.Contacts.Delete.message) { + deleteConfirmation + } + } + } } @ViewBuilder private var deleteConfirmation: some View { diff --git a/Monal/another.im/Views/Main/MainTabScreen.swift b/Monal/another.im/Views/Main/MainTabScreen.swift index 1e968ea..c2d0c95 100644 --- a/Monal/another.im/Views/Main/MainTabScreen.swift +++ b/Monal/another.im/Views/Main/MainTabScreen.swift @@ -27,8 +27,7 @@ struct MainTabScreen: View { // ChatsListScreen() case .contacts: - Text("contacts") - // ContactsScreen() + ContactsScreen() case .settings: Text("settings") diff --git a/Monal/another.im/XMPP/MonalWrapperModels.swift b/Monal/another.im/XMPP/MonalWrapperModels.swift index 5198c21..fa5fab3 100644 --- a/Monal/another.im/XMPP/MonalWrapperModels.swift +++ b/Monal/another.im/XMPP/MonalWrapperModels.swift @@ -1,5 +1,8 @@ import Foundation +import monalxmpp +import SwiftUI +// MARK: - Account enum AccountsAvailability { case noAccounts case allDisabled @@ -32,8 +35,22 @@ struct Account: Identifiable { } } -struct Contact: Identifiable { - let jid: String - - var id: String { jid } +extension Account: UniversalInputSelectionElement { + var icon: Image? { nil } + var text: String? { jid } +} + +// MARK: - Contact +struct Contact: Identifiable { + let ownerId: Int + let contactJid: String + let name: String? + + var id: String { contactJid } + + init?(_ obj: MLContact) { + ownerId = obj.accountID.intValue + contactJid = obj.contactJid + name = obj.nickName + } } diff --git a/Monal/another.im/XMPP/MonalXmppWrapper.swift b/Monal/another.im/XMPP/MonalXmppWrapper.swift index 14d72bd..0634dc6 100644 --- a/Monal/another.im/XMPP/MonalXmppWrapper.swift +++ b/Monal/another.im/XMPP/MonalXmppWrapper.swift @@ -10,7 +10,6 @@ final class MonalXmppWrapper: ObservableObject { private let db: DataLayer private var notificationObservers: [AnyObject] = [] - private var isInitialized: Bool = false init() { // here is some inits (just for now) @@ -88,33 +87,43 @@ private final class LoginTry { // MARK: - Handle notifications private extension MonalXmppWrapper { func subscribeToUpdates() { + // General let generalRefresh = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalRefresh), object: nil, queue: .main) { [weak self] _ in - // get accounts - let accounts = self?.db.accountList() - .compactMap { dict -> Account? in - guard let dict = dict as? NSDictionary else { return nil } - return Account(dict) - } ?? [] - self?.accounts = accounts - - // start connect if it initialization process - if !(self?.isInitialized ?? true) { - self?.xmpp.reconnectAll() - self?.isInitialized = true - } - - // mark accounts availability - if accounts.isEmpty { - self?.accountsAvailability = .noAccounts - } else if !accounts.filter({ $0.isEnabled }).isEmpty { - self?.accountsAvailability = .allDisabled - } else { - self?.accountsAvailability = .someEnabled - } - - // get contacts for active accounts - // + self?.refreshAccounts() + self?.refreshContacts() } notificationObservers.append(generalRefresh) + + // For contacts + let contactRefresh = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalContactRefresh), object: nil, queue: .main) { [weak self] _ in + self?.refreshContacts() + } + let contactRemove = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalContactRemoved), object: nil, queue: .main) { [weak self] _ in + self?.refreshContacts() + } + notificationObservers.append(contentsOf: [contactRefresh, contactRemove]) + } + + func refreshAccounts() { + let accounts = db.accountList() + .compactMap { dict -> Account? in + guard let dict = dict as? NSDictionary else { return nil } + return Account(dict) + } + self.accounts = accounts + xmpp.connectIfNecessary() + + // mark accounts availability + if accounts.isEmpty { + accountsAvailability = .noAccounts + } else if accounts.filter({ $0.isEnabled }).isEmpty { + accountsAvailability = .allDisabled + } else { + accountsAvailability = .someEnabled + } + } + + func refreshContacts() { + contacts = db.contactList().compactMap { Contact($0) } } }