From 6a0622f47a2abdfc50b6b9773a5df63aea1aff7d Mon Sep 17 00:00:00 2001 From: talksik Date: Tue, 28 Dec 2021 14:47:51 -0800 Subject: [PATCH 1/5] getting states read --- .../Views/InnerCircle/InnerCircleView.swift | 1 + nirvana-ios/Views/Router/RouterView.swift | 29 ++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift index eca06f5..3074fa3 100644 --- a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift +++ b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift @@ -13,6 +13,7 @@ struct InnerCircleView: View { @EnvironmentObject var authSessionStore: AuthSessionStore @EnvironmentObject var navigationStack: NavigationStack + @State var selectedFriendIndex: String? = nil let universalSize = UIScreen.main.bounds diff --git a/nirvana-ios/Views/Router/RouterView.swift b/nirvana-ios/Views/Router/RouterView.swift index fc28d18..e34e6d8 100644 --- a/nirvana-ios/Views/Router/RouterView.swift +++ b/nirvana-ios/Views/Router/RouterView.swift @@ -19,16 +19,31 @@ enum NavigationPages { struct RouterView: View { @EnvironmentObject var authSessionStore: AuthSessionStore @EnvironmentObject var navigationStack: NavigationStack + @Environment(\.scenePhase) var scenePhase var body: some View { - switch self.authSessionStore.sessionState { - case SessionState.notCheckedYet: - SplashView() - case SessionState.isAuthenticated: - InnerCircleView() - case SessionState.isLoggedOut: - WelcomeView() + ZStack { + switch self.authSessionStore.sessionState { + case SessionState.notCheckedYet: + SplashView() + case SessionState.isAuthenticated: + InnerCircleView() + case SessionState.isLoggedOut: + WelcomeView() + } } + .onChange(of: scenePhase) { newPhase in + if newPhase == .inactive { + print("user is offline") + // set firestore user document isOnline to false + } else if newPhase == .active { + print("user is online again") + // set firestore user document isOnline to true + } else if newPhase == .background { + print("app is in backgroun") + } + } + } } From fa0024eafab4263ec045c6900b17766c2a89362a Mon Sep 17 00:00:00 2001 From: talksik Date: Tue, 28 Dec 2021 15:29:03 -0800 Subject: [PATCH 2/5] updating in the right spots --- nirvana-ios/Models/User.swift | 8 ++++++ .../Repositories/FirestoreService.swift | 14 ++++++++++- nirvana-ios/Services/AuthSessionStore.swift | 25 +++++++++++++++++++ .../Views/InnerCircle/InnerCircleView.swift | 5 ++++ nirvana-ios/Views/Router/RouterView.swift | 3 +++ 5 files changed, 54 insertions(+), 1 deletion(-) diff --git a/nirvana-ios/Models/User.swift b/nirvana-ios/Models/User.swift index 3a2c842..c824f3a 100644 --- a/nirvana-ios/Models/User.swift +++ b/nirvana-ios/Models/User.swift @@ -10,6 +10,12 @@ import Firebase import FirebaseFirestore import FirebaseFirestoreSwift +enum UserStatus: String, Codable { + case online + case offline + case background +} + struct User: Identifiable, Codable { @DocumentID var id: String? var nickname: String? @@ -17,6 +23,8 @@ struct User: Identifiable, Codable { var emailAddress:String? var avatar:String? var deviceToken: String? + + var userStatus: UserStatus? @ServerTimestamp var lastLoggedInTimestamp: Date? @ServerTimestamp var createdTimestamp: Date? diff --git a/nirvana-ios/Repositories/FirestoreService.swift b/nirvana-ios/Repositories/FirestoreService.swift index 82308da..9a7b480 100644 --- a/nirvana-ios/Repositories/FirestoreService.swift +++ b/nirvana-ios/Repositories/FirestoreService.swift @@ -155,7 +155,10 @@ class FirestoreService { completion(ServiceState.error(ServiceError(description: error.localizedDescription))) } } - +} + +// updates to user +extension FirestoreService { func updateUserDeviceToken(userId: String, deviceToken: String, completion: @escaping((_ state: ServiceState) -> ())) { do { if userId != nil { @@ -170,4 +173,13 @@ class FirestoreService { } } + func updateUserStatus(userId: String, userStatus: UserStatus, completion: @escaping((_ state: ServiceState) -> ())) { + do { + let _ = try db.collection(Collection.users.rawValue).document(userId).setData(["userStatus": userStatus.rawValue], merge: true) + completion(ServiceState.success("Updated user status")) + } catch { + print("error in updating user \(error.localizedDescription)") + completion(ServiceState.error(ServiceError(description: error.localizedDescription))) + } + } } diff --git a/nirvana-ios/Services/AuthSessionStore.swift b/nirvana-ios/Services/AuthSessionStore.swift index fc79324..0a92dd3 100644 --- a/nirvana-ios/Services/AuthSessionStore.swift +++ b/nirvana-ios/Services/AuthSessionStore.swift @@ -411,3 +411,28 @@ extension AuthSessionStore { self.listenersActive = false } } + + +// manage whether user is online or not +extension AuthSessionStore { + func updateUserStatus(userStatus: UserStatus) { + // if user is authenticated + if self.sessionState == .isAuthenticated { + // if user already has the status that we want to update to, no need to update + // remember that the user is updated realtime so we have the newest user data + if self.user?.userStatus == userStatus { + // do nothing...already have this status + print("user already has status: \(userStatus)") + } + else { + if let uid = self.getCurrentUserId() { + self.firestoreService.updateUserStatus(userId: uid, userStatus: userStatus) {res in + print(res) + } + } + } + } + + print("not authenticated can't change user status") + } +} diff --git a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift index 3074fa3..539fd1e 100644 --- a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift +++ b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift @@ -135,11 +135,16 @@ struct InnerCircleView: View { } .onAppear { // activate 3 data listeners once for authsessionstore/usermanager if not already called, but authsessionstore will handle that + // TODO: can/should do this on init of view model self.authSessionStore.activateMainDataListeners() // set up push notifications and such + save up to date device token // TODO: this is firing too often? self.innerCircleVM.setUpPushNotifications() + + // set status of user to online + self.authSessionStore.updateUserStatus(userStatus: .online) + } // .onDisappear { // print("deiniting data listeners, but current data should still be cached!") diff --git a/nirvana-ios/Views/Router/RouterView.swift b/nirvana-ios/Views/Router/RouterView.swift index e34e6d8..67e8d48 100644 --- a/nirvana-ios/Views/Router/RouterView.swift +++ b/nirvana-ios/Views/Router/RouterView.swift @@ -36,11 +36,14 @@ struct RouterView: View { if newPhase == .inactive { print("user is offline") // set firestore user document isOnline to false + self.authSessionStore.updateUserStatus(userStatus: .offline) } else if newPhase == .active { print("user is online again") // set firestore user document isOnline to true + self.authSessionStore.updateUserStatus(userStatus: .online) } else if newPhase == .background { print("app is in backgroun") + self.authSessionStore.updateUserStatus(userStatus: .background) } } From df801a443364574ba65000abd7eab16973e482e7 Mon Sep 17 00:00:00 2001 From: talksik Date: Tue, 28 Dec 2021 15:55:13 -0800 Subject: [PATCH 3/5] adding overlay to user icon but still need to view for other users --- .../InnerCircle/CircleNavigationView.swift | 29 +++++++++++++++++-- .../Views/InnerCircle/InnerCircleView.swift | 5 ++-- nirvana-ios/Views/Router/RouterView.swift | 2 ++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift b/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift index 4f341a3..5ef51aa 100644 --- a/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift +++ b/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift @@ -65,7 +65,6 @@ struct CircleNavigationView: View { 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") @@ -75,7 +74,19 @@ struct CircleNavigationView: View { .blur(radius: 5) .frame(width: 40, height: 40) .clipShape(Circle()) - .padding(5) + .overlay(alignment: .bottomTrailing) { + // user status + switch self.authSessionStore.user?.userStatus { + case .online: + Circle() + .frame(width: 10, height: 10) + .foregroundColor(Color.green) + case .offline: + Circle() + default: + Circle() + } + } } else { Image((self.authSessionStore.user?.avatar)!) .resizable() @@ -83,8 +94,20 @@ struct CircleNavigationView: View { .background(self.innerCircleVM.isRecording ? Color.orange : NirvanaColor.teal.opacity(0.5)) .frame(width: 40, height: 40) .clipShape(Circle()) - .padding(5) .shadow(radius: 10) + .overlay(alignment: .bottomTrailing) { + // user status + switch self.authSessionStore.user?.userStatus { + case .online: + Circle() + .frame(width: 10, height: 10) + .foregroundColor(Color.green) + case .offline: + Circle() + default: + Circle() + } + } } } diff --git a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift index 539fd1e..63b5fc0 100644 --- a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift +++ b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift @@ -92,11 +92,10 @@ struct InnerCircleView: View { } // header - VStack(alignment: .leading) { + ZStack(alignment: .topLeading) { + Color.clear CircleNavigationView(alertActive: self.$alertActive, alertText: self.$alertText, alertSubtext: self.$alertSubtext).environmentObject(innerCircleVM) - - Spacer() } CircleFooterView(selectedFriendIndex: self.$selectedFriendIndex).environmentObject(innerCircleVM) diff --git a/nirvana-ios/Views/Router/RouterView.swift b/nirvana-ios/Views/Router/RouterView.swift index 67e8d48..6c25859 100644 --- a/nirvana-ios/Views/Router/RouterView.swift +++ b/nirvana-ios/Views/Router/RouterView.swift @@ -42,6 +42,8 @@ struct RouterView: View { // set firestore user document isOnline to true self.authSessionStore.updateUserStatus(userStatus: .online) } else if newPhase == .background { + // TODO: find a way to do this in the background so that people can call me and start talking even if it's in the background + // but this may just not be possible, only with an actual call so to speak print("app is in backgroun") self.authSessionStore.updateUserStatus(userStatus: .background) } From b60f7e77b394f50f1b128b56a0bf466b5e22c762 Mon Sep 17 00:00:00 2001 From: talksik Date: Tue, 28 Dec 2021 16:36:40 -0800 Subject: [PATCH 4/5] basic status showing based on friend status --- nirvana-ios/Services/AuthSessionStore.swift | 79 +++++++++++++------ .../Views/InnerCircle/CircleGridView.swift | 13 ++- .../InnerCircle/CircleNavigationView.swift | 2 +- .../Views/InnerCircle/InnerCircleView.swift | 2 +- 4 files changed, 66 insertions(+), 30 deletions(-) diff --git a/nirvana-ios/Services/AuthSessionStore.swift b/nirvana-ios/Services/AuthSessionStore.swift index 0a92dd3..fe3fb3e 100644 --- a/nirvana-ios/Services/AuthSessionStore.swift +++ b/nirvana-ios/Services/AuthSessionStore.swift @@ -42,6 +42,8 @@ final class AuthSessionStore: ObservableObject, SessionStore { @Published var relevantUsersDict: [String: User] = [:] // all cached/snapshotted users from db for app use @Published var relevantMessagesByUserDict: [String: [Message]] = [:] // note, this is all messages related to me + private var friendsListeners: [ListenerRegistration] = [] + private var dataListeners: [ListenerRegistration] = [] private var listenersActive = false // false on app/processes killed @@ -126,7 +128,7 @@ final class AuthSessionStore: ObservableObject, SessionStore { do { try Auth.auth().signOut() - self.deinitDataListeners() + self.deinitAllDataListeners() } catch let signOutError as NSError { print("Error signing out: %@", signOutError) } @@ -243,6 +245,9 @@ extension AuthSessionStore { self.userFriendsDict.removeAll() self.friendsArr.removeAll() // updating to keep the friends in order + // clear the listeners for the the previous set of users + self.deinitFriendListeners() + for document in querySnapshot!.documents { let userFriend:UserFriends? = try? document.data(as: UserFriends.self) @@ -252,37 +257,49 @@ extension AuthSessionStore { self.userFriendsDict[userFriend!.friendId] = userFriend! - // get user and initialize this user in dict - self.db.collection("users").document(userFriend!.friendId).getDocument { (document, error) in - if let document = document, document.exists { - let returnedUser = try? document.data(as: User.self) + // making sure that I clear the inbox if this user came from the inbox... + // could be an accepted or rejected user + if self.inboxUsersArr.contains(userFriend!.friendId) { + self.inboxUsersArr = self.inboxUsersArr.filter {inboxUserId in + return inboxUserId != userFriend!.friendId + } + } + + // only get friend's info if is an active friend + if !userFriend!.isActive { + continue + } + + // create a listener for this specific user + let currFriendListener = self.db.collection("users").document(userFriend!.friendId) + .addSnapshotListener {documentSnapshot, error in + guard let document = documentSnapshot else { + print("Error fetching friend's data: \(error!)") + return + } - DispatchQueue.main.async { - if returnedUser != nil { - self.relevantUsersDict[userFriend!.friendId] = returnedUser! - - // making sure that I clear the inbox if this user came from the inbox...regardless of - // whether I accepted or rejected them - if self.inboxUsersArr.contains(userFriend!.friendId) { - self.inboxUsersArr = self.inboxUsersArr.filter {inboxUserId in - return inboxUserId != userFriend!.friendId - } - } - - if userFriend!.isActive { - self.friendsArr.append(userFriend!.friendId) - } + guard let data = document.data() else { + print("friend realtime data was empty.") + return + } + + let updatedReturnedUser = try? document.data(as: User.self) + + if updatedReturnedUser != nil { + DispatchQueue.main.async { + self.relevantUsersDict[userFriend!.friendId] = updatedReturnedUser! + + self.friendsArr.append(userFriend!.friendId) self.objectWillChange.send() - print("added this user to the array of users for user's circle\(returnedUser?.nickname)") + print("added this user to the array of users for user's circle\(updatedReturnedUser?.nickname)") } } - } else { - print("user doesn't exist from user friend relationship") - // should not happen } - } + + // adding to list of current friend listeners + self.friendsListeners.append(currFriendListener) } } @@ -401,11 +418,21 @@ extension AuthSessionStore { } - func deinitDataListeners() { + func deinitFriendListeners() { + for listener in self.friendsListeners { + listener.remove() + } + } + + func deinitAllDataListeners() { + // get rid of the main listeners for listener in self.dataListeners { listener.remove() } + // get rid of the inner listeners + self.deinitFriendListeners() + print("deactivated listeners!") self.listenersActive = false diff --git a/nirvana-ios/Views/InnerCircle/CircleGridView.swift b/nirvana-ios/Views/InnerCircle/CircleGridView.swift index 5c1aafd..12de40b 100644 --- a/nirvana-ios/Views/InnerCircle/CircleGridView.swift +++ b/nirvana-ios/Views/InnerCircle/CircleGridView.swift @@ -80,8 +80,15 @@ struct CircleGridView: View { .blur(radius: 8) .cornerRadius(100) - // check if the last message in the conversation between me and my friend was me talking or him - if self.haveNewMessageFromFriend(friendDbId: friendId) { // him talking + // this friend is online + if self.authSessionStore.relevantUsersDict[friendId]?.userStatus == .online { + Circle() + .frame(width: 20, height: 20) + .foregroundColor(Color.green) + .font(.title2) + .padding(5) + } + else if self.haveNewMessageFromFriend(friendDbId: friendId) { // check if the last message in the conversation between me and my friend was me talking or him Image(systemName: "arrow.down.left.circle.fill") .foregroundColor(Color.orange) .font(.title2) @@ -370,6 +377,8 @@ extension CircleGridView { extension CircleGridView { // listening to messages private func handleTap(gridItemIndex: Int, friendId: String) { + // TODO: if there is a message, then listen to it + // then after that, check if friend is online, and if so, then connect with him/her if they are free/not in another call print("tap gesture activated") // clearing the player to make room for this friend's convo or to deselect this user diff --git a/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift b/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift index 5ef51aa..0255d52 100644 --- a/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift +++ b/nirvana-ios/Views/InnerCircle/CircleNavigationView.swift @@ -95,7 +95,7 @@ struct CircleNavigationView: View { .frame(width: 40, height: 40) .clipShape(Circle()) .shadow(radius: 10) - .overlay(alignment: .bottomTrailing) { + .overlay(alignment: .topTrailing) { // user status switch self.authSessionStore.user?.userStatus { case .online: diff --git a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift index 63b5fc0..a8b9767 100644 --- a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift +++ b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift @@ -149,7 +149,7 @@ struct InnerCircleView: View { // print("deiniting data listeners, but current data should still be cached!") // // // deactivate all data listeners -// self.authSessionStore.deinitDataListeners() +// self.authSessionStore.deinitAllDataListeners() // } } } From 5d143ea2e88895fcb9bf33e682ab91c7771d59e7 Mon Sep 17 00:00:00 2001 From: talksik Date: Tue, 28 Dec 2021 16:49:19 -0800 Subject: [PATCH 5/5] fixing logic to make sure there are no duplicates in the friends arr --- nirvana-ios/Services/AuthSessionStore.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nirvana-ios/Services/AuthSessionStore.swift b/nirvana-ios/Services/AuthSessionStore.swift index fe3fb3e..f60989a 100644 --- a/nirvana-ios/Services/AuthSessionStore.swift +++ b/nirvana-ios/Services/AuthSessionStore.swift @@ -288,8 +288,13 @@ extension AuthSessionStore { if updatedReturnedUser != nil { DispatchQueue.main.async { self.relevantUsersDict[userFriend!.friendId] = updatedReturnedUser! - - self.friendsArr.append(userFriend!.friendId) + + // update current friendsArr if the friend is already there + if let indexFriend = self.friendsArr.firstIndex(of: userFriend!.friendId) { + print("friend already in the friends arr, just needed to update the relevantusersdict") + } else { // new friend not in array + self.friendsArr.append(userFriend!.friendId) + } self.objectWillChange.send()