From 081ab46a86aaa8d26ce68d5c4f49b7c11436d96f Mon Sep 17 00:00:00 2001 From: talksik Date: Mon, 20 Dec 2021 15:41:27 -0800 Subject: [PATCH] getting to decent place now --- nirvana-ios.xcodeproj/project.pbxproj | 12 +- nirvana-ios/Globals/StringExtension.swift | 28 ++++ nirvana-ios/Models/Contact.swift | 17 -- .../Repositories/FirestoreService.swift | 22 +++ .../Views/Contacts/ContactsPickerView.swift | 158 +++++++++--------- nirvana-ios/Views/Contacts/ContactsView.swift | 2 +- .../Views/Contacts/ContactsViewModel.swift | 62 ++++++- .../Views/Contacts/FindFriendsView.swift | 79 +++++++++ .../Views/InnerCircle/InnerCircleView.swift | 4 +- 9 files changed, 272 insertions(+), 112 deletions(-) create mode 100644 nirvana-ios/Globals/StringExtension.swift delete mode 100644 nirvana-ios/Models/Contact.swift create mode 100644 nirvana-ios/Views/Contacts/FindFriendsView.swift diff --git a/nirvana-ios.xcodeproj/project.pbxproj b/nirvana-ios.xcodeproj/project.pbxproj index ac71bf7..75335e9 100644 --- a/nirvana-ios.xcodeproj/project.pbxproj +++ b/nirvana-ios.xcodeproj/project.pbxproj @@ -33,6 +33,8 @@ 896CE7002770847400DB474E /* CircleFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE6FF2770847400DB474E /* CircleFooterView.swift */; }; 896CE7022770892F00DB474E /* CircleGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE7012770892F00DB474E /* CircleGridView.swift */; }; 896CE7052770AE5D00DB474E /* InboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE7042770AE5D00DB474E /* InboxView.swift */; }; + 896CE70727711BAB00DB474E /* FindFriendsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE70627711BAB00DB474E /* FindFriendsView.swift */; }; + 896CE70927712CED00DB474E /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE70827712CED00DB474E /* StringExtension.swift */; }; 89AD5573276DD790001658E0 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5572276DD790001658E0 /* SplashView.swift */; }; 89AD5575276DE724001658E0 /* PhoneCodeVerificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5574276DE724001658E0 /* PhoneCodeVerificationView.swift */; }; 89AD5578276DEAB3001658E0 /* LogoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AD5577276DEAB3001658E0 /* LogoHeaderView.swift */; }; @@ -50,7 +52,6 @@ 89BDCDEF2769B50C00577829 /* ContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BDCDEE2769B50C00577829 /* ContactsView.swift */; }; 89BDCDF12769E64200577829 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BDCDF02769E64200577829 /* Messages.swift */; }; 89F05B9B276A87920002E9C7 /* ContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */; }; - 89F05B9F276A8D360002E9C7 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05B9E276A8D360002E9C7 /* Contact.swift */; }; 89F05BA4276ACC060002E9C7 /* ContactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05BA3276ACC060002E9C7 /* ContactService.swift */; }; 89F05BA6276ACD830002E9C7 /* ContactsPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */; }; 89F1386D2767EB19006680ED /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F1386C2767EB19006680ED /* SignInView.swift */; }; @@ -105,6 +106,8 @@ 896CE6FF2770847400DB474E /* CircleFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleFooterView.swift; sourceTree = ""; }; 896CE7012770892F00DB474E /* CircleGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleGridView.swift; sourceTree = ""; }; 896CE7042770AE5D00DB474E /* InboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxView.swift; sourceTree = ""; }; + 896CE70627711BAB00DB474E /* FindFriendsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindFriendsView.swift; sourceTree = ""; }; + 896CE70827712CED00DB474E /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; 89AD5572276DD790001658E0 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; 89AD5574276DE724001658E0 /* PhoneCodeVerificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneCodeVerificationView.swift; sourceTree = ""; }; 89AD5577276DEAB3001658E0 /* LogoHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoHeaderView.swift; sourceTree = ""; }; @@ -121,7 +124,6 @@ 89BDCDEE2769B50C00577829 /* ContactsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsView.swift; sourceTree = ""; }; 89BDCDF02769E64200577829 /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; 89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsViewModel.swift; sourceTree = ""; }; - 89F05B9E276A8D360002E9C7 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; 89F05BA3276ACC060002E9C7 /* ContactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactService.swift; sourceTree = ""; }; 89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsPickerView.swift; sourceTree = ""; }; 89F1386C2767EB19006680ED /* SignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInView.swift; sourceTree = ""; }; @@ -270,6 +272,7 @@ 89BDCDE62769616600577829 /* UIImage+Extension.swift */, 89F18AEB276E92BF00115B1E /* CustomExtensions.swift */, 894E80BD276ED7A700624B72 /* avatars.swift */, + 896CE70827712CED00DB474E /* StringExtension.swift */, ); path = Globals; sourceTree = ""; @@ -279,7 +282,6 @@ children = ( 89BDCDF02769E64200577829 /* Messages.swift */, 891A160D27657CEA00B26659 /* User.swift */, - 89F05B9E276A8D360002E9C7 /* Contact.swift */, ); path = Models; sourceTree = ""; @@ -379,6 +381,7 @@ 89BDCDEE2769B50C00577829 /* ContactsView.swift */, 89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */, 89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */, + 896CE70627711BAB00DB474E /* FindFriendsView.swift */, ); path = Contacts; sourceTree = ""; @@ -525,8 +528,9 @@ 896CE7002770847400DB474E /* CircleFooterView.swift in Sources */, 894E80BE276ED7A700624B72 /* avatars.swift in Sources */, 896CE7052770AE5D00DB474E /* InboxView.swift in Sources */, - 89F05B9F276A8D360002E9C7 /* Contact.swift in Sources */, 891A160327656FC700B26659 /* HomeView.swift in Sources */, + 896CE70927712CED00DB474E /* StringExtension.swift in Sources */, + 896CE70727711BAB00DB474E /* FindFriendsView.swift in Sources */, 891A160B2765732700B26659 /* HomeViewModel.swift in Sources */, 89BCABEB276D2A09009E9B10 /* PhoneVerificationView.swift in Sources */, 892179E62765FECD001E6C60 /* FooterControlsViewModel.swift in Sources */, diff --git a/nirvana-ios/Globals/StringExtension.swift b/nirvana-ios/Globals/StringExtension.swift new file mode 100644 index 0000000..35cbc8a --- /dev/null +++ b/nirvana-ios/Globals/StringExtension.swift @@ -0,0 +1,28 @@ +// +// StringExtension.swift +// nirvana-ios +// +// Created by Arjun Patel on 12/20/21. +// + +import Foundation + +extension String { + func matchingStrings(regex: String) -> [[String]] { + guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] } + let nsString = self as NSString + let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)) + return results.map { result in + (0.. ())) { + // TODO: validation here as well to verify that phone number string is even valid + db.collection(Collection.users.rawValue).whereField("phoneNumber", isEqualTo: phoneNumber) + .getDocuments() { (querySnapshot, err) in + if let err = err { + print("Error getting documents: \(err)") + completion(nil) + } else { + if !(querySnapshot?.isEmpty)! { + print("existing user found!!!!: \(querySnapshot?.documents)") + + for document in querySnapshot!.documents { + let returnedUser = try? document.data(as: User.self) + completion(returnedUser) + } + } else { + completion(nil) + } + } + } + } func createUser(user: User) { do { diff --git a/nirvana-ios/Views/Contacts/ContactsPickerView.swift b/nirvana-ios/Views/Contacts/ContactsPickerView.swift index 07af937..e0840f4 100644 --- a/nirvana-ios/Views/Contacts/ContactsPickerView.swift +++ b/nirvana-ios/Views/Contacts/ContactsPickerView.swift @@ -1,82 +1,82 @@ +//// +//// ContactsPickerView.swift +//// nirvana-ios +//// +//// Created by Arjun Patel on 12/15/21. +//// // -// ContactsPickerView.swift -// nirvana-ios +//import SwiftUI +//import Contacts // -// Created by Arjun Patel on 12/15/21. +//struct ContactsPickerView: View { +// @Environment(\.dismiss) var dismiss // - -import SwiftUI -import Contacts - -struct ContactsPickerView: View { - @Environment(\.dismiss) var dismiss - - // updates the parent view for it to do something with the new contact - @State var selectedContact: CNContact? - - var contacts: [String: [CNContact]] = loadContactsGrouped() - - var body: some View { - let keys = (Array(contacts.keys) as [String]).sorted() - NavigationView { - List { - ForEach(keys.sorted(), id: \.self) { key in - Section(header: Text(key)) { - ForEach(contacts[key] ?? [CNContact](), id: \.identifier) { contact in - ContactRow(contact: contact, selectedContact: self.$selectedContact) - } - } - } - } - .listStyle(GroupedListStyle()) - .navigationTitle("All Contacts".localized) - .navigationBarItems(trailing: Button(action: { - dismiss() - - }, label: { - Text("Cancel".localized) - })) - } - } - - static func loadContactsGrouped() -> [String: [CNContact]] { - let provider = ContactsViewModel() - let contacts = provider.fetchContacts() - - let group = Dictionary(grouping: contacts) { (contact) -> String in - return String(contact.familyName.first ?? "?") - } - - print(group) - - return group - } -} - - -struct ContactRow: View { - var contact: CNContact - @Binding var selectedContact: CNContact? - - var body: some View { - Button(action: { - selectContact() - }) { - HStack { - Text("\(contact.familyName)").fontWeight(.bold) - Text("\(contact.givenName) \(contact.middleName)") - } - .foregroundColor(.black) - } - } - - func selectContact() { - self.selectedContact = self.contact - } -} - -extension String { - var localized: String { - return NSLocalizedString(self, comment: "") - } -} +// // updates the parent view for it to do something with the new contact +// @State var selectedContact: CNContact? +// +// var contacts: [String: [CNContact]] = loadContactsGrouped() +// +// var body: some View { +// let keys = (Array(contacts.keys) as [String]).sorted() +// NavigationView { +// List { +// ForEach(keys.sorted(), id: \.self) { key in +// Section(header: Text(key)) { +// ForEach(contacts[key] ?? [CNContact](), id: \.identifier) { contact in +// ContactRow(contact: contact, selectedContact: self.$selectedContact) +// } +// } +// } +// } +// .listStyle(GroupedListStyle()) +// .navigationTitle("All Contacts".localized) +// .navigationBarItems(trailing: Button(action: { +// dismiss() +// +// }, label: { +// Text("Cancel".localized) +// })) +// } +// } +// +// static func loadContactsGrouped() -> [String: [CNContact]] { +// let provider = ContactsViewModel() +// let contacts = provider.fetchContacts() +// print(contacts) +// let group = Dictionary(grouping: contacts) { (contact) -> String in +// return String(contact.familyName.first ?? "?") +// } +// +// print(group) +// +// return group +// } +//} +// +// +//struct ContactRow: View { +// var contact: CNContact +// @Binding var selectedContact: CNContact? +// +// var body: some View { +// Button(action: { +// selectContact() +// }) { +// HStack { +// Text("\(contact.familyName)").fontWeight(.bold) +// Text("\(contact.givenName) \(contact.middleName)") +// } +// .foregroundColor(.black) +// } +// } +// +// func selectContact() { +// self.selectedContact = self.contact +// } +//} +// +//extension String { +// var localized: String { +// return NSLocalizedString(self, comment: "") +// } +//} diff --git a/nirvana-ios/Views/Contacts/ContactsView.swift b/nirvana-ios/Views/Contacts/ContactsView.swift index b704ba1..0dd46f3 100644 --- a/nirvana-ios/Views/Contacts/ContactsView.swift +++ b/nirvana-ios/Views/Contacts/ContactsView.swift @@ -42,7 +42,7 @@ struct ContactsView: View { .shadow(radius:10) }) - Text(selectedContact != nil ? "Selected: \((selectedContact?.familyName)!) \((selectedContact?.givenName)!)" : "Nothing selected".localized) + Text(selectedContact != nil ? "Selected: \((selectedContact?.familyName)!) \((selectedContact?.givenName)!)" : "Nothing selected") Button { self.navigationStack.push(InnerCircleView()) diff --git a/nirvana-ios/Views/Contacts/ContactsViewModel.swift b/nirvana-ios/Views/Contacts/ContactsViewModel.swift index 2873d21..f2e4ac6 100644 --- a/nirvana-ios/Views/Contacts/ContactsViewModel.swift +++ b/nirvana-ios/Views/Contacts/ContactsViewModel.swift @@ -11,6 +11,9 @@ import SwiftUI class ContactsViewModel : ObservableObject { @Published var showPermissionAlert = false + @Published var contacts: [ContactsViewModelContact] = [] + + private var firestoreService = FirestoreService() let store: CNContactStore = CNContactStore() let keys: [CNKeyDescriptor] = [CNContactImageDataKey as CNKeyDescriptor, @@ -18,6 +21,10 @@ class ContactsViewModel : ObservableObject { CNContactEmailAddressesKey as CNKeyDescriptor, CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] + init() { + self.fetchContacts() + } + func openSettings() { guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } if UIApplication.shared.canOpenURL(settingsURL) { UIApplication.shared.open(settingsURL)} @@ -31,7 +38,7 @@ class ContactsViewModel : ObservableObject { } } - func fetchContacts(sortOrder: CNContactSortOrder = .userDefault) -> [CNContact] { + func fetchContacts() { let authStatus = CNContactStore.authorizationStatus(for: .contacts) if authStatus == .notDetermined { @@ -42,20 +49,57 @@ class ContactsViewModel : ObservableObject { } let fetchRequest = CNContactFetchRequest(keysToFetch: keys) - fetchRequest.sortOrder = sortOrder - - var fetchedContacts = [CNContact]() + fetchRequest.sortOrder = .userDefault do { - try store.enumerateContacts(with: fetchRequest) { - (contact, stop) in - fetchedContacts.append(contact) + try store.enumerateContacts(with: fetchRequest) {(contact, stop) in + // only checking if american number or not for now + var cnPhoneNumber = contact.phoneNumbers.first?.value.stringValue + let contactDisplayName = contact.givenName + // strip out nondigits + // add or keep country code + + // TODO: take out hard coding to US country code + var formattedNumber = cnPhoneNumber?.digits.suffix(10) + + // only show contacts if they have a phone number + if formattedNumber != nil { + formattedNumber = "+1" + formattedNumber! // adding country code + + let contactNumber = String(formattedNumber!) + var contactVm = ContactsViewModelContact(cnName: contactDisplayName, cnPhoneNumber: contactNumber) + + print(contactVm) + // check if the contact is an existing user by phone number + + + self.firestoreService.getUserByPhoneNumber(phoneNumber: contactNumber) {[weak self] returnedUser in + print(returnedUser) + DispatchQueue.main.async { + if returnedUser == nil { + // do nothing, keep contactsVm object user property nil + } else { + // adding user details if this contact is existing user + contactVm.user = returnedUser + } + + self?.contacts.append(contactVm) + } + } + + } } } catch { print("Unable to fetch contacts. \(error)") } - - return fetchedContacts } } + + +struct ContactsViewModelContact : Identifiable { + var id : String = UUID().uuidString + var user: User? + var cnName: String + var cnPhoneNumber: String +} diff --git a/nirvana-ios/Views/Contacts/FindFriendsView.swift b/nirvana-ios/Views/Contacts/FindFriendsView.swift new file mode 100644 index 0000000..787d87a --- /dev/null +++ b/nirvana-ios/Views/Contacts/FindFriendsView.swift @@ -0,0 +1,79 @@ +// +// FindFriendsView.swift +// nirvana-ios +// +// Created by Arjun Patel on 12/20/21. +// + +import SwiftUI +import Contacts + + + +struct FindFriendsView: View { + @Environment(\.dismiss) var dismiss + + @StateObject var contactsVM = ContactsViewModel() + + @State private var searchQuery: String = "" + + + var body: some View { + NavigationView { + // list with search bar + List { + ForEach(contactsVM.contacts) { contact in + Text(contact.cnName) + } + } + .searchable(text: $searchQuery) + .listStyle(GroupedListStyle()) + .navigationTitle("Find Friends") + .navigationBarItems(trailing: Button(action: { + dismiss() + }, label: { + Text("Cancel") + })) + } + .onAppear { + // organize contacts + // sort by has account and then show all other contacts + + } + } +} + +struct FindFriendsView_Previews: PreviewProvider { + static var previews: some View { + FindFriendsView().environmentObject(AuthSessionStore()) + } +} + +struct ListContactRow: View { + var contact: CNContact + + @State var showAlert = false + @State var alertText = "" + @State var alertMessage = "" + + var body: some View { + Button(action: { + + }) { + HStack { + Text("\(contact.familyName)").fontWeight(.bold) + Text("\(contact.givenName) \(contact.middleName)") + + Spacer() + + // if the user is already an existing user + } + .foregroundColor(.black) + } + .alert(self.alertText, isPresented: self.$showAlert) { + Button("OK", role: ButtonRole.cancel) { } + } message: { + Text(self.alertMessage) + } + } +} diff --git a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift index d835d7a..9f7cf3a 100644 --- a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift +++ b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift @@ -18,7 +18,7 @@ struct InnerCircleView: View { @EnvironmentObject var authSessionStore: AuthSessionStore @EnvironmentObject var navigationStack: NavigationStack - @State var sheetView: SheetView? = nil + @State var sheetView: SheetView? = .contacts let universalSize = UIScreen.main.bounds @@ -43,7 +43,7 @@ struct InnerCircleView: View { .sheet(item: self.$sheetView) {page in switch page { case SheetView.contacts: - ContactsPickerView() + FindFriendsView() case SheetView.inbox: InboxView()// test one for now default: