diff --git a/nirvana-ios.xcodeproj/project.pbxproj b/nirvana-ios.xcodeproj/project.pbxproj index 4c9975d..66a97fa 100644 --- a/nirvana-ios.xcodeproj/project.pbxproj +++ b/nirvana-ios.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 8904DEC8277055E90071E6CA /* OnboardingTrioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8904DEC7277055E90071E6CA /* OnboardingTrioView.swift */; }; + 89110714277166D6005797FA /* UserFriends.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89110713277166D6005797FA /* UserFriends.swift */; }; 891A15E127653A7000B26659 /* nirvana_iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891A15E027653A7000B26659 /* nirvana_iosApp.swift */; }; 891A15E327653A7000B26659 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891A15E227653A7000B26659 /* ContentView.swift */; }; 891A15E527653A7100B26659 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 891A15E427653A7100B26659 /* Assets.xcassets */; }; @@ -29,6 +30,12 @@ 894E80BE276ED7A700624B72 /* avatars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80BD276ED7A700624B72 /* avatars.swift */; }; 894E80C1276EFD0500624B72 /* AudioPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80C0276EFD0400624B72 /* AudioPlayerView.swift */; }; 894E80C327702E5900624B72 /* AddBasicInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894E80C227702E5800624B72 /* AddBasicInfoViewModel.swift */; }; + 896CE6FE27707E6B00DB474E /* CircleNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896CE6FD27707E6B00DB474E /* CircleNavigationView.swift */; }; + 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 */; }; @@ -46,7 +53,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 */; }; @@ -74,6 +80,7 @@ /* Begin PBXFileReference section */ 8904DEC7277055E90071E6CA /* OnboardingTrioView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingTrioView.swift; sourceTree = ""; }; + 89110713277166D6005797FA /* UserFriends.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFriends.swift; sourceTree = ""; }; 891A15DD27653A7000B26659 /* nirvana-ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "nirvana-ios.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 891A15E027653A7000B26659 /* nirvana_iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = nirvana_iosApp.swift; sourceTree = ""; }; 891A15E227653A7000B26659 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -97,6 +104,12 @@ 894E80BD276ED7A700624B72 /* avatars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = avatars.swift; sourceTree = ""; }; 894E80C0276EFD0400624B72 /* AudioPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerView.swift; sourceTree = ""; }; 894E80C227702E5800624B72 /* AddBasicInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBasicInfoViewModel.swift; sourceTree = ""; }; + 896CE6FD27707E6B00DB474E /* CircleNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleNavigationView.swift; sourceTree = ""; }; + 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 = ""; }; @@ -113,7 +126,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 = ""; }; @@ -205,6 +217,7 @@ 891A15F62765639B00B26659 /* Views */ = { isa = PBXGroup; children = ( + 896CE7032770AE3400DB474E /* Inbox */, 894E80BF276EFCF500624B72 /* AudioPlayer */, 89F18AF3276ECF1800115B1E /* AddBasicInfo */, 89F18AF2276ECD5D00115B1E /* OnBoarding */, @@ -261,6 +274,7 @@ 89BDCDE62769616600577829 /* UIImage+Extension.swift */, 89F18AEB276E92BF00115B1E /* CustomExtensions.swift */, 894E80BD276ED7A700624B72 /* avatars.swift */, + 896CE70827712CED00DB474E /* StringExtension.swift */, ); path = Globals; sourceTree = ""; @@ -270,7 +284,7 @@ children = ( 89BDCDF02769E64200577829 /* Messages.swift */, 891A160D27657CEA00B26659 /* User.swift */, - 89F05B9E276A8D360002E9C7 /* Contact.swift */, + 89110713277166D6005797FA /* UserFriends.swift */, ); path = Models; sourceTree = ""; @@ -301,6 +315,14 @@ path = AudioPlayer; sourceTree = ""; }; + 896CE7032770AE3400DB474E /* Inbox */ = { + isa = PBXGroup; + children = ( + 896CE7042770AE5D00DB474E /* InboxView.swift */, + ); + path = Inbox; + sourceTree = ""; + }; 89AD5571276DD77D001658E0 /* SplashView */ = { isa = PBXGroup; children = ( @@ -331,6 +353,9 @@ children = ( 89BCABE4276B3D58009E9B10 /* InnerCircleView.swift */, 89BCABE6276B3D64009E9B10 /* InnerCircleViewModel.swift */, + 896CE6FD27707E6B00DB474E /* CircleNavigationView.swift */, + 896CE6FF2770847400DB474E /* CircleFooterView.swift */, + 896CE7012770892F00DB474E /* CircleGridView.swift */, ); path = InnerCircle; sourceTree = ""; @@ -359,6 +384,7 @@ 89BDCDEE2769B50C00577829 /* ContactsView.swift */, 89F05B9A276A87920002E9C7 /* ContactsViewModel.swift */, 89F05BA5276ACD830002E9C7 /* ContactsPickerView.swift */, + 896CE70627711BAB00DB474E /* FindFriendsView.swift */, ); path = Contacts; sourceTree = ""; @@ -502,9 +528,13 @@ buildActionMask = 2147483647; files = ( 891A15E327653A7000B26659 /* ContentView.swift in Sources */, + 896CE7002770847400DB474E /* CircleFooterView.swift in Sources */, 894E80BE276ED7A700624B72 /* avatars.swift in Sources */, - 89F05B9F276A8D360002E9C7 /* Contact.swift in Sources */, + 896CE7052770AE5D00DB474E /* InboxView.swift in Sources */, 891A160327656FC700B26659 /* HomeView.swift in Sources */, + 896CE70927712CED00DB474E /* StringExtension.swift in Sources */, + 89110714277166D6005797FA /* UserFriends.swift in Sources */, + 896CE70727711BAB00DB474E /* FindFriendsView.swift in Sources */, 891A160B2765732700B26659 /* HomeViewModel.swift in Sources */, 89BCABEB276D2A09009E9B10 /* PhoneVerificationView.swift in Sources */, 892179E62765FECD001E6C60 /* FooterControlsViewModel.swift in Sources */, @@ -520,6 +550,7 @@ 89BDCDE527695DB900577829 /* NirvanaImage.swift in Sources */, 891A16132765A44300B26659 /* ChatsCarouselViewModel.swift in Sources */, 89F05B9B276A87920002E9C7 /* ContactsViewModel.swift in Sources */, + 896CE6FE27707E6B00DB474E /* CircleNavigationView.swift in Sources */, 89AD5573276DD790001658E0 /* SplashView.swift in Sources */, 89F18AEC276E92BF00115B1E /* CustomExtensions.swift in Sources */, 89F18AF1276ECAA300115B1E /* RouterView.swift in Sources */, @@ -528,6 +559,7 @@ 89BCABED276D2B05009E9B10 /* WavesGlassBackgroundView.swift in Sources */, 89F05BA6276ACD830002E9C7 /* ContactsPickerView.swift in Sources */, 8904DEC8277055E90071E6CA /* OnboardingTrioView.swift in Sources */, + 896CE7022770892F00DB474E /* CircleGridView.swift in Sources */, 89BDCDEA27697F9C00577829 /* UserMenu.swift in Sources */, 89BCABE7276B3D64009E9B10 /* InnerCircleViewModel.swift in Sources */, 89AD557C276DF86A001658E0 /* FirestoreService.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)! { + for document in querySnapshot!.documents { + let returnedUser = try? document.data(as: User.self) + completion(returnedUser) + } + } else { + completion(nil) + } + } + } + } func createUser(user: User) { do { @@ -81,4 +103,21 @@ class FirestoreService { return FieldValue.serverTimestamp() } + + + func createOrUpdateUserFriends(userFriend: UserFriends, completion: @escaping((_ state: ServiceState) -> ())) { + do { + if userFriend.id != nil { + let _ = try db.collection(Collection.userFriends.rawValue).addDocument(from: userFriend) + completion(ServiceState.success("created/updated userfriend in firestore service")) + } else { + completion(ServiceState.error(ServiceError(description: "No userfriend id given to firestore service"))) + } + } catch { + print("error in creating user friend \(error.localizedDescription)") + completion(ServiceState.error(ServiceError(description: error.localizedDescription))) + } + + } + } diff --git a/nirvana-ios/Services/AuthSessionStore.swift b/nirvana-ios/Services/AuthSessionStore.swift index 99db033..a6f4f77 100644 --- a/nirvana-ios/Services/AuthSessionStore.swift +++ b/nirvana-ios/Services/AuthSessionStore.swift @@ -121,26 +121,34 @@ final class AuthSessionStore: ObservableObject, SessionStore { func setupAuthListen() { // monitor authentication changes using firebase - self.handler = Auth.auth().addStateDidChangeListener { [weak self] res, user in + self.handler = Auth.auth().addStateDidChangeListener { [weak self] res, authUser in print("auth listener activated") guard let self = self else { return } - self.sessionState = user == nil ? SessionState.isLoggedOut : SessionState.isAuthenticated + self.sessionState = authUser == nil ? SessionState.isLoggedOut : SessionState.isAuthenticated // get the user from the auth table in firebase auth - if let uid = user?.uid { + if let uid = authUser?.uid { // if we have a user, create a new user model print("auth listener: Got user: \(uid)") - print("auth listener: phone number: \(user?.phoneNumber)") + print("auth listener: phone number: \(authUser?.phoneNumber)") // when we have a user get signed in, we can activate the listener for fetching the user's data to keep our auth session store always up to date for all of the ui self.firestoreService.getUserRealtime(userId: uid) {[weak self] realtimeUpdatedUser in - if realtimeUpdatedUser != nil { - print("up to date user: \(realtimeUpdatedUser)") - self?.user = realtimeUpdatedUser + DispatchQueue.main.async { + if realtimeUpdatedUser != nil { + print("up to date user: \(realtimeUpdatedUser)") + self?.user = realtimeUpdatedUser + } } } + + // TODO: listener for getting user's sent and received messages + + // TODO: listener for getting user's circle members + + } else { // if we don't have a user, set our session to nil self.user = nil diff --git a/nirvana-ios/Views/AddBasicInfo/AddBasicInfoView.swift b/nirvana-ios/Views/AddBasicInfo/AddBasicInfoView.swift index 394168d..6bff78b 100644 --- a/nirvana-ios/Views/AddBasicInfo/AddBasicInfoView.swift +++ b/nirvana-ios/Views/AddBasicInfo/AddBasicInfoView.swift @@ -102,7 +102,7 @@ struct AddBasicInfoView: View { Spacer() - + // button to save information Button { print("attempting to save information") diff --git a/nirvana-ios/Views/Contacts/ContactsPickerView.swift b/nirvana-ios/Views/Contacts/ContactsPickerView.swift index bb89b93..e0840f4 100644 --- a/nirvana-ios/Views/Contacts/ContactsPickerView.swift +++ b/nirvana-ios/Views/Contacts/ContactsPickerView.swift @@ -1,78 +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 ContactPickerView: View { - @Binding var showPicker: Bool - // updates the parent view for it to do something with the new contact - @Binding 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, showPicker: self.$showPicker, selectedContact: self.$selectedContact) - } - } - } - } - .listStyle(GroupedListStyle()) - .navigationTitle("All Contacts".localized) - .navigationBarItems(trailing: Button(action: {self.showPicker = false}, 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 ?? "?") - } - - return group - } -} - - -struct ContactRow: View { - var contact: CNContact - @Binding var showPicker: Bool - @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 - self.showPicker = false - } -} - -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 f376d14..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()) @@ -53,7 +53,7 @@ struct ContactsView: View { Spacer() } .sheet(isPresented: self.$showPicker) { - ContactPickerView(showPicker: self.$showPicker, selectedContact: self.$selectedContact) +// ContactPickerView(showPicker: self.$showPicker, selectedContact: self.$selectedContact) } .padding() .background(NirvanaColor.bgLightGrey) diff --git a/nirvana-ios/Views/Contacts/ContactsViewModel.swift b/nirvana-ios/Views/Contacts/ContactsViewModel.swift index f792014..ea01d5a 100644 --- a/nirvana-ios/Views/Contacts/ContactsViewModel.swift +++ b/nirvana-ios/Views/Contacts/ContactsViewModel.swift @@ -11,17 +11,18 @@ import SwiftUI class ContactsViewModel : ObservableObject { @Published var showPermissionAlert = false + @Published var contacts: [String: ContactsViewModelContact] = [:] - let store: CNContactStore - let keys: [CNKeyDescriptor] + private var firestoreService = FirestoreService() + + let store: CNContactStore = CNContactStore() + let keys: [CNKeyDescriptor] = [CNContactImageDataKey as CNKeyDescriptor, + CNContactPhoneNumbersKey as CNKeyDescriptor, + CNContactEmailAddressesKey as CNKeyDescriptor, + CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] init() { - store = CNContactStore() - - keys = [CNContactImageDataKey as CNKeyDescriptor, - CNContactPhoneNumbersKey as CNKeyDescriptor, - CNContactEmailAddressesKey as CNKeyDescriptor, - CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] + self.fetchContacts() } func openSettings() { @@ -30,37 +31,86 @@ class ContactsViewModel : ObservableObject { } func requestAccess() { - store.requestAccess(for: .contacts) { - (granted, error) in + + store.requestAccess(for: .contacts) {(granted, error) in + // TODO: do something useful with completionHandler return } - // TODO do something useful with completionHandler } - func fetchContacts(sortOrder: CNContactSortOrder = .userDefault) -> [CNContact] { + func fetchContacts() { let authStatus = CNContactStore.authorizationStatus(for: .contacts) + if authStatus == .notDetermined { requestAccess() } else if authStatus == .restricted { - // TODO prompt user that app is useless without access to contacts + // TODO: prompt user that app is useless without access to contacts self.showPermissionAlert = true } 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 + " " + contact.familyName + // 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?.count ?? 0 > 9 { + formattedNumber = "+1" + formattedNumber! // adding country code + + let contactNumber = String(formattedNumber!) + var contactVm = ContactsViewModelContact(cnName: contactDisplayName, cnPhoneNumber: contactNumber, sortingProp: contactDisplayName) + + // check if the contact is an existing user by phone number + self.firestoreService.getUserByPhoneNumber(phoneNumber: contactNumber) {[weak self] returnedUser in + DispatchQueue.main.async { + if returnedUser == nil { + // do nothing, keep contactsVm object user property nil + contactVm.isExisting = false + contactVm.sortingProp = contactVm.cnName + } else { + // adding user details if this contact is existing user + contactVm.sortingProp = "!" // adding for sorting purposes + contactVm.user = returnedUser + contactVm.isExisting = true + print("existing user: \(contactVm.cnName)") + } + + self?.contacts[contactVm.sortingProp] = contactVm + } + } + } } } catch { print("Unable to fetch contacts. \(error)") } + } + + func addOrActivateFriendToCircle(userId: String, friendId: String) { + // setting timestamps to nil to make sure that new server timestamp is set + var userFriend = UserFriends(userId: userId, friendId: friendId, isActive: true, lastUpdatedTimestamp: nil, createdTimestamp: nil) - return fetchedContacts + self.firestoreService.createOrUpdateUserFriends(userFriend: userFriend) {[weak self] res in + print(res) + } } } + + +struct ContactsViewModelContact : Identifiable { + var id : String = UUID().uuidString + var user: User? + var cnName: String + var cnPhoneNumber: String + var isExisting: Bool = false + var sortingProp: String +} diff --git a/nirvana-ios/Views/Contacts/FindFriendsView.swift b/nirvana-ios/Views/Contacts/FindFriendsView.swift new file mode 100644 index 0000000..2bb5542 --- /dev/null +++ b/nirvana-ios/Views/Contacts/FindFriendsView.swift @@ -0,0 +1,148 @@ +// +// FindFriendsView.swift +// nirvana-ios +// +// Created by Arjun Patel on 12/20/21. +// + +import SwiftUI +import Contacts +import NavigationStack + + + +struct FindFriendsView: View { + @Environment(\.dismiss) var dismiss + + @StateObject var contactsVM = ContactsViewModel() + + @State private var searchQuery: String = "" + + var body: some View { + + NavigationView { + // TODO: add search bar + have secondary sort + List { + ForEach(searchItems, id: \.self) { key in + if let currContact = self.contactsVM.contacts[key] { + ListContactRow(contactsVM: self.contactsVM, contact: currContact) + } + } + } + .searchable(text: self.$searchQuery) + .navigationTitle("Find Friends") + .navigationBarItems(trailing: Button(action: { + dismiss() + }, label: { + Text("Cancel") + })) + } + } + + var searchItems: [String] { + let keys = (Array(self.contactsVM.contacts.keys) as [String]).sorted() + + if self.searchQuery.isEmpty { + return keys + } else { + return keys.filter { $0.contains(self.searchQuery) } + } + } + + +} + +struct FindFriendsView_Previews: PreviewProvider { + static var previews: some View { + FindFriendsView().environmentObject(AuthSessionStore()) + } +} + +struct ListContactRow: View { + @EnvironmentObject var navigationStack : NavigationStack + @EnvironmentObject var authSessionStore: AuthSessionStore + + var contactsVM: ContactsViewModel + var contact:ContactsViewModelContact + + @State var showAlert = false + @State var alertText = "🌱Confirm Arjun into Your Circle?" + @State var alertMessage = "Note: You can have a maximium of 12 people in your circle and you have 5 swaps left!" + + var body: some View { + if contact.isExisting { // add to circle + // TODO: check if this contact is already in user's circle + HStack(alignment: .center, spacing: 0) { + Image(contact.user?.avatar ?? Avatars.avatarSystemNames[1]) + .resizable() + .scaledToFit() + .clipShape(Circle()) + .frame(width: 40, height: 40) + .padding(.trailing, 10) + + Text(contact.cnName) + .font(.headline) + + Spacer() + + Button { + print("showing alert now") + // TODO: show alert if circle is full + self.showAlert.toggle() + + } label: { + Label("Add", systemImage: "plus.circle") + .font(.title2) + .foregroundColor(NirvanaColor.solidTeal) + } + } + .alert(self.alertText, isPresented: self.$showAlert) { + Button("Add \(contact.cnName)") { + print("adding contact to circle") + // call method in vm to get it done, then navigate to the circle + self.contactsVM.addOrActivateFriendToCircle(userId: self.authSessionStore.user!.id!, friendId: (contact.user!.id)!) + + self.navigationStack.push(InnerCircleView()) + } + } message: { + Text(self.alertMessage) + } + } + else { // invite button to text the person + HStack(alignment: .center, spacing: 0) { + Image(Avatars.avatarSystemNames[1]) + .resizable() + .scaledToFit() + .clipShape(Circle()) + .frame(width: 40, height: 40) + .blur(radius: 8) + .padding(.trailing, 10) + + Text(contact.cnName) + .font(.headline) + + Spacer() + + Button { + print("inviting user now") + // text message to him/her + + if contact.cnPhoneNumber != nil { + let sms: String = "sms://\(contact.cnPhoneNumber)&body=Join me on Nirvana! I sent you a message there! https://usenirvana.com" + let strUrl: String = sms.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + + UIApplication.shared.open(URL(string: strUrl)!, options: [:], completionHandler: nil) + } else { + print("phone number is nil of contact") + } + } label: { + Label("Invite", systemImage: "paperplane") + .font(.title2) + .foregroundColor(Color.orange) + } + } + } + } +} + + diff --git a/nirvana-ios/Views/Inbox/InboxView.swift b/nirvana-ios/Views/Inbox/InboxView.swift new file mode 100644 index 0000000..4292bbe --- /dev/null +++ b/nirvana-ios/Views/Inbox/InboxView.swift @@ -0,0 +1,21 @@ +// +// InboxView.swift +// nirvana-ios +// +// Created by Arjun Patel on 12/20/21. +// + +import SwiftUI + +struct InboxView: View { + var body: some View { + Text("This is the inbox where you can see a bunch of inboxes") + //TODO: add illustration if there is no one in inbox, but have button to open sheet for contacts with the sheet view object + } +} + +struct InboxView_Previews: PreviewProvider { + static var previews: some View { + InboxView() + } +} diff --git a/nirvana-ios/Views/InnerCircle/CircleFooterView.swift b/nirvana-ios/Views/InnerCircle/CircleFooterView.swift new file mode 100644 index 0000000..8f7110f --- /dev/null +++ b/nirvana-ios/Views/InnerCircle/CircleFooterView.swift @@ -0,0 +1,73 @@ +// +// CircleFooterView.swift +// nirvana-ios +// +// Created by Arjun Patel on 12/20/21. +// + +import SwiftUI +import NavigationStack + +struct CircleFooterView: View { + @EnvironmentObject var navigationStack: NavigationStack + @EnvironmentObject var authSessionStore: AuthSessionStore + + + var body: some View { + // TODO: do cool animations with this footer background fill in while recording or playing a message + ZStack(alignment:.bottomTrailing) { + VStack(alignment:.center) { + Spacer() + + HStack { + Image("Artboards_Diversity_Avatars_by_Netguru-1") + .resizable() + .scaledToFit() + .background(NirvanaColor.teal.opacity(0.1)) + .frame(width: 40, height: 40) + .clipShape(Circle()) + .padding(5) + + + + VStack (alignment: .leading) { + Text("Sarth Shah") + .font(.footnote) + .foregroundColor(NirvanaColor.light) + Text("sent 20 minutes ago") + .font(.caption) + .foregroundColor(.gray) + } + + Spacer() + + Label("Replay", systemImage: "repeat.1.circle.fill") + .font(.title2) + .labelStyle(.iconOnly) + .foregroundColor(Color.orange) + + + Spacer(minLength: 65) + } + .frame(maxWidth: .infinity, maxHeight: 60) // 60 is the height of the footer control big circle + .background(Color.white.opacity(0.5)) + .clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous)) + .shadow(color: Color.black.opacity(0.25), radius: 30, x: 0, y: 20) + } + + FooterControlsView() + } + .padding() +// .offset( +// x:0, +// y:self.selectedUserIndex == nil ? 150 : 0 +// ) +// .animation(Animation.spring(), value: self.selectedUserIndex) + } +} + +struct CircleFooterView_Previews: PreviewProvider { + static var previews: some View { + CircleFooterView() + } +} diff --git a/nirvana-ios/Views/InnerCircle/CircleGridView.swift b/nirvana-ios/Views/InnerCircle/CircleGridView.swift new file mode 100644 index 0000000..930e6ff --- /dev/null +++ b/nirvana-ios/Views/InnerCircle/CircleGridView.swift @@ -0,0 +1,192 @@ +// +// CircleGridView.swift +// nirvana-ios +// +// Created by Arjun Patel on 12/20/21. +// + +import SwiftUI +import NavigationStack + +struct CircleGridView: View { + @EnvironmentObject var authSessionStore: AuthSessionStore + @EnvironmentObject var navigationStack: NavigationStack + + let universalSize = UIScreen.main.bounds + + // magic variables for grid + // TODO: make the top left person or one horizontal person be in the center of screen...makes the top honeycomb pop + private static var numberOfItems: Int = 12 + private static let size: CGFloat = UIScreen.main.bounds.height*0.15 // scaling with screen size + private static let spacingBetweenColumns: CGFloat = 0 + private static let spacingBetweenRows: CGFloat = 0 + private static let totalColumns: Int = Int(log2(Double(Self.numberOfItems))) // scaling the circles and calculating column count + + @State private var selectedUserIndex: Int? = nil + + // TODO: change the size to adaptive or something to make outer ring items shrink their overall size and fit better + let gridItems: [GridItem] = Array( + repeating: GridItem(.fixed(Self.size), spacing: spacingBetweenColumns, alignment: .center), + count: totalColumns) + + private let big:CGFloat = 1 + private let medium:CGFloat = 0.75 + private let small:CGFloat = 0.5 + private let supersmall:CGFloat = 0.3 + + // TODO: centered honeycomb + side: maybe have two honeycombs: one in the center of 7 people and then everyone else surrounding + // TODO: maybe make our "center" offset to a little to the top left to make it more honeycomb from top left + private let center: CGPoint = CGPoint(x: UIScreen.main.bounds.width*0.5,y: UIScreen.main.bounds.height*0.5) + + var body: some View { + gridContent + } + + private var gridContent: some View { + // main communication hub + // TODO: client side, sort the honeycomb from top left to bottom right + // based on most close friends to least + ScrollViewReader {scrollReaderValue in + ScrollView([.horizontal, .vertical], showsIndicators: false) { + LazyVGrid( + columns: gridItems, + alignment: .center, + spacing: Self.spacingBetweenRows + ) { + ForEach(0.. Color { + if (userIndex == self.selectedUserIndex) { // user clicked on this user + return NirvanaColor.dimTeal.opacity(0.4) + } +// else if self.usersWithNewMessage.contains(userIndex) { // this user has a message +// return Color.orange.opacity(0.8) +// } + + return Color.white.opacity(0.4) + } + + // getting the proxy of an individual item + // and decoding into a scale that the item should take + private func getScale(proxy: GeometryProxy, itemNumber: Int) -> CGFloat { + // if this user is selected + if itemNumber == self.selectedUserIndex { + return big + 0.2 + } + + // if this user that we are rendering + // has a new message for us + // TODO: too much memory usage doing this across the board +// if self.usersWithNewMessage.contains(itemNumber) { +// return big + 0.2 +// } + + let posRelToGrid = proxy.frame(in: .global) // relative to the entire screen + let midX = getRowNumber(itemNumber) % 2 == 0 ? posRelToGrid.midX + (Self.size / 2) + (Self.spacingBetweenColumns / 2) : posRelToGrid.midX + let midY = posRelToGrid.midY + + let xdelta = abs(midX - self.center.x) + let ydelta = abs(midY - self.center.y) + + let innerCircleXAcceptance = Self.size * 0.75 + let innerCircleYAcceptance = Self.size * 0.75 + let outerRingXAcceptance = self.universalSize.width * 0.4 + let outerRingYAcceptance = self.universalSize.height * 0.25 + let wayOutRingXAcceptance = self.universalSize.width * 0.425 + let wayOutRingYAcceptance = self.universalSize.height * 0.375 + + + if xdelta <= innerCircleXAcceptance && ydelta < innerCircleYAcceptance { + return big + } else if xdelta <= outerRingXAcceptance && ydelta < outerRingYAcceptance { + return medium + } else if xdelta <= wayOutRingXAcceptance && ydelta < wayOutRingYAcceptance { + return small + } + return supersmall + } + + private static func distanceBetweenPoints(p1: CGPoint, p2: CGPoint) -> CGFloat { + let xDist = abs(p2.x - p1.x) + let yDist = abs(p2.y - p2.y) + + return CGFloat( + sqrt( + pow(xDist, 2) + pow(yDist, 2) + ) + ) + } + //get row number given a value/item number in the list + private func getRowNumber(_ value: Int) -> Int { + return value / Self.totalColumns + } + private func honeycombOffSetX(_ value: Int) -> CGFloat { + let rowNumber = self.getRowNumber(value) + + // make every even row have the honeycomb offset + if rowNumber % 2 == 0 { + return Self.size / 2 + Self.spacingBetweenColumns / 2 // account for the column spacing from before + } + + return 0 + } +} + +struct CircleGridView_Previews: PreviewProvider { + static var previews: some View { + CircleGridView() + } +} diff --git a/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift b/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift new file mode 100644 index 0000000..a292ade --- /dev/null +++ b/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift @@ -0,0 +1,150 @@ +// +// NavigationView.swift +// nirvana-ios +// +// Created by Arjun Patel on 12/20/21. +// + +import SwiftUI +import NavigationStack + +struct CircleNavigationView: View { + @EnvironmentObject var navigationStack: NavigationStack + @EnvironmentObject var authSessionStore: AuthSessionStore + + @Binding var sheetView: SheetView? + + var body: some View { + HStack(alignment: .center) { + Menu { + Button(role: .destructive) { + print("manage inbox") + self.sheetView = .inbox + } label: { + Label("inbox", systemImage: "envelope.badge") + .foregroundColor(NirvanaColor.teal) + } + + Button { + print("going to show contacts now") + self.sheetView = .contacts + } label: { + Label("add peeps", systemImage: "person.2.wave.2") + .foregroundColor(NirvanaColor.teal) + } + + Button { + self.navigationStack.push(AddBasicInfoView()) + } label: { + Label("profile", systemImage: "person.crop.circle") + .foregroundColor(NirvanaColor.teal) + } + + Button { + self.navigationStack.push(OnboardingTrioView()) + } label: { + Label("why?", systemImage: "leaf") + .foregroundColor(NirvanaColor.teal) + } + + Button { + print("navigate to usenirvana.com") + if let url = URL(string: "https://usenirvana.com") { + UIApplication.shared.open(url) + } + + } label: { + Label("about us", systemImage: "globe.americas") + .foregroundColor(NirvanaColor.teal) + } + + Button(role: .destructive) { + print("log out button clicked") + + self.authSessionStore.logOut() + + // once logged out, then the listener will listen and router view will take care of us thereafter + } label: { + Label("log out", systemImage: "rectangle.portrait.and.arrow.right") + .foregroundColor(NirvanaColor.teal) + } + + } label: { + if self.authSessionStore.user?.avatar == nil { + Image("Artboards_Diversity_Avatars_by_Netguru-1") + .resizable() + .scaledToFit() + .background(NirvanaColor.teal.opacity(0.1)) + .blur(radius: 5) + .frame(width: 40, height: 40) + .clipShape(Circle()) + .padding(5) + } else { + Image((self.authSessionStore.user?.avatar)!) + .resizable() + .scaledToFit() + .background(NirvanaColor.teal.opacity(0.5)) + .frame(width: 40, height: 40) + .clipShape(Circle()) + .padding(5) + } + } + + ScrollView(.horizontal) { + HStack { + Button { + + } label: { + Label("circle", systemImage: "peacesign") + .font(.caption2) + .foregroundColor(Color.white) + .padding(.vertical, 10) + .padding(.horizontal, 8) + .background( + RoundedRectangle(cornerRadius: 20, style: .continuous) + .fill(Color.teal.opacity(0.5)) + ).overlay( + RoundedRectangle(cornerRadius: 20, style: .continuous) + .strokeBorder(Color.white.opacity(01), lineWidth: 1) + ) + } + + Button { + + } label: { + Label("groups", systemImage: "rectangle.3.group") + .font(.caption2) + .foregroundColor(Color.gray) + .padding(.vertical, 10) + .padding(.horizontal, 8) + .overlay( + RoundedRectangle(cornerRadius: 20, style: .continuous) + .strokeBorder(Color.white.opacity(1), lineWidth: 1) + ) + } + + Button { + + } label: { + Label("work", systemImage: "suitcase") + .font(.caption2) + .foregroundColor(Color.gray) + .padding(.vertical, 10) + .padding(.horizontal, 8) + .overlay( + RoundedRectangle(cornerRadius: 20, style: .continuous) + .strokeBorder(Color.white.opacity(1), lineWidth: 1) + ) + } + } + } + } + .padding(.horizontal) + } +} + +struct CircleNavigationView_Previews: PreviewProvider { + static var previews: some View { + CircleNavigationView(sheetView: .constant(SheetView.contacts)).environmentObject(AuthSessionStore(isPreview: true)) + } +} diff --git a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift index 8190c3d..fe62572 100644 --- a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift +++ b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift @@ -8,15 +8,19 @@ import SwiftUI import NavigationStack +enum SheetView : Identifiable { + var id: Self { self } + case contacts + case inbox +} + struct InnerCircleView: View { @EnvironmentObject var authSessionStore: AuthSessionStore @EnvironmentObject var navigationStack: NavigationStack - let universalSize = UIScreen.main.bounds + @State var sheetView: SheetView? = nil - // TESTING - // users who will have a thumping thing because they sent a message - @State var usersWithNewMessage: [Int] = [] + let universalSize = UIScreen.main.bounds var body: some View { ZStack { @@ -24,356 +28,31 @@ struct InnerCircleView: View { WavesGlassBackgroundView() // content - gridContent + CircleGridView() // header VStack(alignment: .leading) { - header - bubbleNavigation + CircleNavigationView(sheetView: self.$sheetView) Spacer() } -// Path { path in -// -// //draw the axes -// path.move(to: CGPoint(x:0, y:universalSize.height*0.5 )) -// -// path.addLine(to: CGPoint(x:universalSize.width, y: universalSize.height*0.5)) -// -// path.move(to: CGPoint(x:universalSize.width*0.5, y:0 )) -// -// path.addLine(to: CGPoint(x:universalSize.width * 0.5, y: universalSize.height)) -// -// -// //TODO: draw the acceptance boxes -// } -// .stroke(Color.black) -// .edgesIgnoringSafeArea(.all) - - footerNavigation + CircleFooterView() } - .onAppear() { - // TESTING - // fake like these users sent a message to view - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { - self.usersWithNewMessage = [1, 5, 10] - } - } - } - - // TODO: do cool animations with this footer background fill in while - // recording or playing a message - var footerNavigation: some View { - ZStack(alignment:.bottomTrailing) { - VStack(alignment:.center) { - Spacer() - - HStack { - if self.selectedUserIndex != nil { - Image("Artboards_Diversity_Avatars_by_Netguru-\(self.selectedUserIndex! + 1)") - .resizable() - .scaledToFit() - .background(NirvanaColor.teal.opacity(0.1)) - .frame(width: 40, height: 40) - .clipShape(Circle()) - .padding(5) - } - - - VStack (alignment: .leading) { - Text("Sarth Shah") - .font(.footnote) - .foregroundColor(NirvanaColor.light) - Text("sent 20 minutes ago") - .font(.caption) - .foregroundColor(.gray) - } - - Spacer() - - Label("Replay", systemImage: "repeat.1.circle.fill") - .font(.title2) - .labelStyle(.iconOnly) - .foregroundColor(Color.orange) - - - Spacer(minLength: 65) - } - .frame(maxWidth: .infinity, maxHeight: 60) // 60 is the height of the footer control big circle - .background(Color.white.opacity(0.5)) - .clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous)) - .shadow(color: Color.black.opacity(0.25), radius: 30, x: 0, y: 20) - } - - FooterControlsView() - } - .padding() - .offset( - x:0, - y:self.selectedUserIndex == nil ? 150 : 0 - ) - .animation(Animation.spring(), value: self.selectedUserIndex) - } - - var bubbleNavigation : some View { - HStack(alignment: .center) { - Button { - - } label: { - Label("circle", systemImage: "peacesign") - .font(.caption2) - .foregroundColor(Color.white) - .padding(.vertical, 10) - .padding(.horizontal, 8) - .background( - RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(Color.teal.opacity(0.5)) - ).overlay( - RoundedRectangle(cornerRadius: 20, style: .continuous) - .strokeBorder(Color.white.opacity(01), lineWidth: 1) - ) - } - - Button { - - } label: { - Label("groups", systemImage: "rectangle.3.group") - .font(.caption2) - .foregroundColor(Color.gray) - .padding(.vertical, 10) - .padding(.horizontal, 8) - .overlay( - RoundedRectangle(cornerRadius: 20, style: .continuous) - .strokeBorder(Color.white.opacity(1), lineWidth: 1) - ) - } - - Button { - - } label: { - Label("work", systemImage: "suitcase") - .font(.caption2) - .foregroundColor(Color.gray) - .padding(.vertical, 10) - .padding(.horizontal, 8) - .overlay( - RoundedRectangle(cornerRadius: 20, style: .continuous) - .strokeBorder(Color.white.opacity(1), lineWidth: 1) - ) - } - } - .padding(.horizontal) - } - - // magic variables for grid - // TODO: change the number of columns based on the number of items - // TODO: make the top left person or one horizontal person be in the center of screen...makes the top honeycomb pop - private static var numberOfItems: Int = 12 - private static let size: CGFloat = UIScreen.main.bounds.height*0.15 // scaling with screen size - private static let spacingBetweenColumns: CGFloat = 0 - private static let spacingBetweenRows: CGFloat = 0 - private static let totalColumns: Int = Int(log2(Double(Self.numberOfItems))) // scaling the circles and calculating column count - - @State private var selectedUserIndex: Int? = nil - - // TODO: change the size to adaptive or something to make outer ring items shrink their overall size and fit better - let gridItems: [GridItem] = Array( - repeating: GridItem(.fixed(Self.size), spacing: spacingBetweenColumns, alignment: .center), - count: totalColumns) - - private let big:CGFloat = 1 - private let medium:CGFloat = 0.75 - private let small:CGFloat = 0.5 - private let supersmall:CGFloat = 0.3 - - // TODO: centered honeycomb + side: maybe have two honeycombs: one in the center of 7 people and then everyone else surrounding - // TODO: maybe make our "center" offset to a little to the top left to make it more honeycomb from top left - private let center: CGPoint = CGPoint(x: UIScreen.main.bounds.width*0.5,y: UIScreen.main.bounds.height*0.5) - - private var gridContent: some View { - // main communication hub - // TODO: client side, sort the honeycomb from top left to bottom right - // based on most close friends to least - ScrollViewReader {scrollReaderValue in - ScrollView([.horizontal, .vertical], showsIndicators: false) { - LazyVGrid( - columns: gridItems, - alignment: .center, - spacing: Self.spacingBetweenRows - ) { - ForEach(0.. Color { - if (userIndex == self.selectedUserIndex) { // user clicked on this user - return NirvanaColor.dimTeal.opacity(0.4) - } else if self.usersWithNewMessage.contains(userIndex) { // this user has a message - return Color.orange.opacity(0.8) - } - - return Color.white.opacity(0.4) - } - - // getting the proxy of an individual item - // and decoding into a scale that the item should take - private func getScale(proxy: GeometryProxy, itemNumber: Int) -> CGFloat { - // if this user is selected - if itemNumber == self.selectedUserIndex { - return big + 0.2 - } - - // if this user that we are rendering - // has a new message for us - // TODO: too much memory usage doing this across the board - if self.usersWithNewMessage.contains(itemNumber) { - return big + 0.2 - } - - let posRelToGrid = proxy.frame(in: .global) // relative to the entire screen - let midX = getRowNumber(itemNumber) % 2 == 0 ? posRelToGrid.midX + (Self.size / 2) + (Self.spacingBetweenColumns / 2) : posRelToGrid.midX - let midY = posRelToGrid.midY - - let xdelta = abs(midX - self.center.x) - let ydelta = abs(midY - self.center.y) - - let innerCircleXAcceptance = Self.size * 0.75 - let innerCircleYAcceptance = Self.size * 0.75 - let outerRingXAcceptance = self.universalSize.width * 0.4 - let outerRingYAcceptance = self.universalSize.height * 0.25 - let wayOutRingXAcceptance = self.universalSize.width * 0.425 - let wayOutRingYAcceptance = self.universalSize.height * 0.375 - - - if xdelta <= innerCircleXAcceptance && ydelta < innerCircleYAcceptance { - return big - } else if xdelta <= outerRingXAcceptance && ydelta < outerRingYAcceptance { - return medium - } else if xdelta <= wayOutRingXAcceptance && ydelta < wayOutRingYAcceptance { - return small - } - return supersmall - } - - private static func distanceBetweenPoints(p1: CGPoint, p2: CGPoint) -> CGFloat { - let xDist = abs(p2.x - p1.x) - let yDist = abs(p2.y - p2.y) - - return CGFloat( - sqrt( - pow(xDist, 2) + pow(yDist, 2) - ) - ) - } - //get row number given a value/item number in the list - private func getRowNumber(_ value: Int) -> Int { - return value / Self.totalColumns - } - private func honeycombOffSetX(_ value: Int) -> CGFloat { - let rowNumber = self.getRowNumber(value) - - // make every even row have the honeycomb offset - if rowNumber % 2 == 0 { - return Self.size / 2 + Self.spacingBetweenColumns / 2 // account for the column spacing from before - } - - return 0 - } - - private var header: some View { - HStack(alignment: .center) { - Image("undraw_handcrafts_leaf") - .resizable() - .frame(width: 20.0, height: 32.132) - .padding(.leading, 20) - - Spacer() - - Image(systemName: "message.and.waveform") - .foregroundColor(Color.red.opacity(1)) - .font(.title2) - - Menu { - Button("Edit Profile") { - self.navigationStack.push(AddBasicInfoView()) - } - - Button("Friends") { - print("manage friends page") - } - - Button("Log out") { - print("log out button clicked") - - self.authSessionStore.logOut() - - // once logged out, then the listener will listen and router view will take care of us thereafter - } - } label: { - Image("Artboards_Diversity_Avatars_by_Netguru-51") - .resizable() - .scaledToFit() - .background(NirvanaColor.teal.opacity(0.1)) - .frame(width: 40, height: 40) - .clipShape(Circle()) - .padding(5) + .sheet(item: self.$sheetView) {page in + switch page { + case SheetView.contacts: + FindFriendsView() + case SheetView.inbox: + InboxView()// test one for now + default: + Text("asdf") } } - .frame(maxWidth: .infinity) - .background(Color.white.opacity(0.5)) - .clipShape(RoundedRectangle(cornerRadius: 30, style: .continuous)) - .shadow(color: Color.black.opacity(0.25), radius: 30, x: 0, y: 20) - .padding(.horizontal) + .onAppear() { + } } }