decent place with the whole refactoring but not done, and not tested at all

This commit is contained in:
talksik
2021-12-24 17:29:00 -08:00
parent e890681839
commit aec0ad9a4f
5 changed files with 124 additions and 109 deletions
+36 -60
View File
@@ -33,13 +33,12 @@ final class AuthSessionStore: ObservableObject, SessionStore {
@Published var sessionState: SessionState = SessionState.notCheckedYet @Published var sessionState: SessionState = SessionState.notCheckedYet
// TODO: figure out which ones to publish // TODO: figure out which ones to publish
@Published var friendsArr: [User] = [] // all active and inactive friends
var messagesArr: [Message] = [] var messagesArr: [Message] = []
@Published var userFriends: [UserFriends] = [] // all active and inactive relationships var userFriendsDict: [String: UserFriends] = [:] // all active and inactive relationships
// transformed data for the views // transformed data for the views
@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 @Published var relevantMessagesByUserDict: [String: [Message]] = [:] // note, this is all messages related to me
@Published var inboxUsers: [User] = []
private var dataListeners: [ListenerRegistration] = [] private var dataListeners: [ListenerRegistration] = []
private var listenersActive = false // false on app/processes killed private var listenersActive = false // false on app/processes killed
@@ -245,8 +244,8 @@ extension AuthSessionStore {
} }
// resetting array to reset friends // resetting array to reset friends
self.friendsArr.removeAll() self.relevantUsersDict.removeAll()
self.userFriends.removeAll() self.userFriendsDict.removeAll()
for document in querySnapshot!.documents { for document in querySnapshot!.documents {
let userFriend:UserFriends? = try? document.data(as: UserFriends.self) let userFriend:UserFriends? = try? document.data(as: UserFriends.self)
@@ -255,15 +254,16 @@ extension AuthSessionStore {
continue continue
} }
self.userFriends.append(userFriend!) self.userFriendsDict[userFriend!.friendId] = userFriend!
// get user and initialize this user in dict // get user and initialize this user in dict
self.db.collection("users").document(userFriend!.friendId).getDocument { (document, error) in self.db.collection("users").document(userFriend!.friendId).getDocument { (document, error) in
if let document = document, document.exists { if let document = document, document.exists {
let returnedUser = try? document.data(as: User.self) let returnedUser = try? document.data(as: User.self)
DispatchQueue.main.async { DispatchQueue.main.async {
if returnedUser != nil { if returnedUser != nil {
self.friendsArr.append(returnedUser!) self.relevantUsersDict[userFriend!.friendId] = returnedUser!
self.objectWillChange.send() self.objectWillChange.send()
@@ -318,7 +318,6 @@ extension AuthSessionStore {
// clearing dict to allow clean list of messages to be put forth // clearing dict to allow clean list of messages to be put forth
//optimize this? but also saving on memory and same db reads //optimize this? but also saving on memory and same db reads
self.relevantMessagesByUserDict.removeAll() self.relevantMessagesByUserDict.removeAll()
self.inboxUsers.removeAll()
self.messagesArr = documents.compactMap { (queryDocumentSnapshot) -> Message? in self.messagesArr = documents.compactMap { (queryDocumentSnapshot) -> Message? in
do { do {
@@ -329,7 +328,7 @@ extension AuthSessionStore {
if currMessage != nil { // not really possible but just check if currMessage != nil { // not really possible but just check
// if the user doesn't exist for the dictionary, then add it // if the user doesn't exist for the dictionary, then add it
// this means it's most likely someone new (never had user_friend relationship before) messaging for the user's inbox // this means it's most likely someone new (never had user_friend relationship before) messaging for the user's inbox
// TODO: prolly want to make a call to get this sender user details for the inbox, but they should either be in the friendsArr or their are not a friend so won't be there // TODO: prolly want to make a call to get this sender user details for the inbox, but they should either be in the friendsDict or their are not a friend so won't be there
// also add in any messages where I am the sender // also add in any messages where I am the sender
if currMessage!.senderId == currUserId { // if I am the sender if currMessage!.senderId == currUserId { // if I am the sender
if self.relevantMessagesByUserDict[currMessage!.receiverId] == nil { if self.relevantMessagesByUserDict[currMessage!.receiverId] == nil {
@@ -345,6 +344,21 @@ extension AuthSessionStore {
self.relevantMessagesByUserDict[currMessage!.senderId]?.append(currMessage!) self.relevantMessagesByUserDict[currMessage!.senderId]?.append(currMessage!)
} }
// need to get and add this user to inbox if they are not a friend, active or inactive
// self.updateInboxUsers(currMessage!.senderId)
// might be an inbox message from someone completely new, but also can be a concurrency thing, where we haven't completely fetched all friends, active or inactive with the previous listener
// no worries though as we keep everything update to date in the view
if !self.relevantUsersDict.keys.contains(currMessage!.senderId) {
self.firestoreService.getUser(userId: currMessage!.senderId) {[weak self] returnedUser in
if returnedUser == nil {
print("couldn't get new user info")
return
}
print("new user for inbox fetched")
self?.relevantUsersDict[currMessage!.senderId] = returnedUser!
}
}
} }
} }
@@ -357,10 +371,6 @@ extension AuthSessionStore {
} }
return nil return nil
} }
// TODO: concurrency problem if the friends array is not filled? prolly not
// get all inbox users' data
self.getIncomingNewMessageUserIds()
} }
self.listenersActive = true self.listenersActive = true
@@ -387,55 +397,21 @@ extension AuthSessionStore {
// transforming data to make it more valuable // transforming data to make it more valuable
extension AuthSessionStore { extension AuthSessionStore {
func getActiveFriends() -> [User] { func getActiveFriendIds() -> [String] {
let activeFriendRelationships = self.userFriends.filter {userFriend in var activeFriendIds: [String] = []
return userFriend.isActive
}
let activeFriendsIds = activeFriendRelationships.map {activeUserFriend in
return activeUserFriend.id
}
// they should already be in the friends arr for (friendId, userFriend) in self.userFriendsDict {
return self.friendsArr.filter {friend in if userFriend.isActive {
return activeFriendsIds.contains(friend.id) activeFriendIds.append(friendId)
}
}
func getInactiveFriends() -> [User] {
let inactiveFriendRelationships = self.userFriends.filter {userFriend in
return !userFriend.isActive
}
let inactiveFriendsIds = inactiveFriendRelationships.map {inactiveUserFriend in
return inactiveUserFriend.id
}
// they should already be in the friends arr
return self.friendsArr.filter {friend in
return inactiveFriendsIds.contains(friend.id)
}
}
// people info of people who are new to me and sent me a message...not active nor inactive
func getIncomingNewMessageUserIds() {
let allFriends = self.friendsArr.map {friend in
return friend.id
}
let allIncomingPeopleIds = self.relevantMessagesByUserDict.keys.filter {friendId in
return !allFriends.contains(friendId)
}
// make call to firestore to get data of each of these people
for newPersonId in allIncomingPeopleIds {
self.firestoreService.getUser(userId: newPersonId) {[weak self] returnedUser in
if returnedUser == nil {
print("couldn't get new user info")
return
}
print("new user for inbox fetched")
self?.inboxUsers.append(returnedUser!)
} }
} }
return activeFriendIds
}
func getInboxUsersIds() -> [String] {
return self.relevantUsersDict.keys.filter {userId in
return !self.userFriendsDict.keys.contains(userId)
}
} }
} }
@@ -58,10 +58,13 @@ struct FindFriendsView: View {
let keys = (Array(self.contactsVM.contacts.keys) as [String]).sorted() let keys = (Array(self.contactsVM.contacts.keys) as [String]).sorted()
// checking if currcontact is already a friend // checking if currcontact is already a friend
let currFriends = self.authSessionStore.friendsArr.map { $0.id } let activeFriendIds = self.authSessionStore.getActiveFriendIds()
let newPotentialFriends = keys.filter {
if let friend = self.contactsVM.contacts[$0]?.user { // seeing if this contact is an existing user let newPotentialFriends = keys.filter {contactSortProp in
if currFriends.contains(friend.id) { // if this is already a friend if let friend = self.contactsVM.contacts[contactSortProp]?.user { // seeing if this contact is an existing user
// if this is already an ACTIVE friend -> don't show
// we want to show inactive friends
if activeFriendIds.contains(friend.id!) {
return false return false
} }
@@ -12,7 +12,7 @@ struct CircleFooterView: View {
@EnvironmentObject var navigationStack: NavigationStack @EnvironmentObject var navigationStack: NavigationStack
@EnvironmentObject var authSessionStore: AuthSessionStore @EnvironmentObject var authSessionStore: AuthSessionStore
@Binding var selectedFriendIndex: Int? @Binding var selectedFriendIndex: String?
@State private var convoRelativeTime = "" @State private var convoRelativeTime = ""
@State private var selectedFriend: User? @State private var selectedFriend: User?
@@ -94,7 +94,7 @@ struct CircleFooterView: View {
// set convo moment/relative time to show // set convo moment/relative time to show
if newValue != nil && myId != nil { if newValue != nil && myId != nil {
self.selectedFriend = self.authSessionStore.friendsArr[newValue!] self.selectedFriend = self.authSessionStore.relevantUsersDict[newValue!]
// get the latest message's timestamp...will be the first in list now // get the latest message's timestamp...will be the first in list now
let lastMessage = self.authSessionStore.relevantMessagesByUserDict[self.selectedFriend!.id!]?.first let lastMessage = self.authSessionStore.relevantMessagesByUserDict[self.selectedFriend!.id!]?.first
@@ -19,25 +19,21 @@ struct CircleGridView: View {
let universalSize = UIScreen.main.bounds let universalSize = UIScreen.main.bounds
var body: some View {
gridContent
}
// magic variables for grid // 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 // 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 @State var numberOfItems: Int = 12
private static let size: CGFloat = UIScreen.main.bounds.height*0.15 // scaling with screen size private static let size: CGFloat = UIScreen.main.bounds.height*0.15 // scaling with screen size
private static let spacingBetweenColumns: CGFloat = 0 private static let spacingBetweenColumns: CGFloat = 0
private static let spacingBetweenRows: 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 private static let totalColumns: Int = 3 // scaling the circles and calculating column count
@Binding var selectedFriendIndex: Int?
// TODO: change the size to adaptive or something to make outer ring items shrink their overall size and fit better // TODO: change the size to adaptive or something to make outer ring items shrink their overall size and fit better
let gridItems: [GridItem] = Array( let gridItems: [GridItem] = Array(
repeating: GridItem(.fixed(Self.size), spacing: spacingBetweenColumns, alignment: .center), repeating: GridItem(.fixed(Self.size), spacing: spacingBetweenColumns, alignment: .center),
count: totalColumns) count: totalColumns)
@Binding var selectedFriendIndex: String?
private let big:CGFloat = 1 private let big:CGFloat = 1
private let medium:CGFloat = 0.75 private let medium:CGFloat = 0.75
private let small:CGFloat = 0.5 private let small:CGFloat = 0.5
@@ -49,10 +45,12 @@ struct CircleGridView: View {
let longPressMinDuration = 0.5 let longPressMinDuration = 0.5
private var gridContent: some View { var body: some View {
// main communication hub // main communication hub
// TODO: client side, sort the honeycomb from top left to bottom right // TODO: client side, sort the honeycomb from top left to bottom right
// based on most close friends to least // based on most close friends to least
let activeFriends = self.authSessionStore.getActiveFriendIds()
let inboxUsers = self.authSessionStore.getInboxUsersIds()
ScrollViewReader {scrollReaderValue in ScrollViewReader {scrollReaderValue in
ScrollView([.horizontal, .vertical], showsIndicators: false) { ScrollView([.horizontal, .vertical], showsIndicators: false) {
LazyVGrid( LazyVGrid(
@@ -60,29 +58,27 @@ struct CircleGridView: View {
alignment: .center, alignment: .center,
spacing: Self.spacingBetweenRows spacing: Self.spacingBetweenRows
) { ) {
ForEach(Array(self.authSessionStore.friendsArr.enumerated()), id: \.offset) { value, friend in // active friends
// for (value, element) in self.authSessionStore.friendMessagesDict.keys.enumerated() { ForEach(0..<activeFriends.count, id: \.self) {value in
let friendId = activeFriends[value]
GeometryReader {gridProxy in GeometryReader {gridProxy in
let scale = getScale(proxy: gridProxy, itemNumber: value) let scale = getScale(proxy: gridProxy, itemNumber: value, userId: friendId)
// TODO: sort the list but may already be sorted from the query and creation of the array of messages?
// shouldn't be nil...hopefully
ZStack(alignment: .topTrailing) { ZStack(alignment: .topTrailing) {
// check if the last message in the conversation between me and my friend was me talking or him // check if the last message in the conversation between me and my friend was me talking or him
// also check if I have listened to it once or twice // also check if I have listened to it once or twice
if self.haveNewMessageFromFriend(friendDbId: friend.id!) { // him talking if self.haveNewMessageFromFriend(friendDbId: friendId) { // him talking
Image(systemName: "wave.3.right.circle.fill") Image(systemName: "wave.3.right.circle.fill")
.foregroundColor(Color.orange) .foregroundColor(Color.orange)
.font(.title) .font(.title)
} }
Circle() Circle()
.foregroundColor(self.getBubbleTint(friendIndex: value, friendDbId: friend.id!)) // different color for a selected user .foregroundColor(self.getBubbleTint(friendIndex: value, friendDbId: friendId)) // different color for a selected user
.blur(radius: 8) .blur(radius: 8)
.cornerRadius(100) .cornerRadius(100)
Image(friend.avatar ?? Avatars.avatarSystemNames[0]) Image(self.authSessionStore.relevantUsersDict[friendId]?.avatar ?? Avatars.avatarSystemNames[0])
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 20) .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 20)
@@ -94,10 +90,10 @@ struct CircleGridView: View {
x: honeycombOffSetX(value), x: honeycombOffSetX(value),
y: 0 y: 0
) )
.id(value) // id for scrollviewreader .id(friendId) // id for scrollviewreader
.frame(height: Self.size) .frame(height: Self.size)
.onTapGesture { .onTapGesture {
self.handleTap(friendIndex: value, friend: friend) self.handleTap(gridItemIndex: value, friendId: friendId)
}// TODO: maybe add simulataneous gesture or sequence? with the tap gesture? }// TODO: maybe add simulataneous gesture or sequence? with the tap gesture?
.gesture( .gesture(
LongPressGesture(minimumDuration: longPressMinDuration) LongPressGesture(minimumDuration: longPressMinDuration)
@@ -106,7 +102,7 @@ struct CircleGridView: View {
self.queuePlayer.removeAllItems() self.queuePlayer.removeAllItems()
print("activated long press!") print("activated long press!")
self.selectedFriendIndex = value self.selectedFriendIndex = friendId
self.activateHaptics() self.activateHaptics()
@@ -137,20 +133,57 @@ struct CircleGridView: View {
self.selectedFriendIndex = nil self.selectedFriendIndex = nil
self.innerCircleVM.stopRecording(senderId: self.authSessionStore.user!.id!, receiver: friend) self.innerCircleVM.stopRecording(senderId: self.authSessionStore.user!.id!, receiver: self.authSessionStore.relevantUsersDict[friendId]!)
// self.recordingGestureDeactived() // self.recordingGestureDeactived()
} }
) )
// .simultaneousGesture( .animation(Animation.spring())
// DragGesture(minimumDistance: 0, coordinateSpace: .local) }
// .onChanged {_ in
// self.recordingGestureActive(friendIndex: value, friend: friend) // ForEach(Array(self.authSessionStore.getInboxUsersIds.enumerated()), id: \.offset) { inboxValue, inboxUser in
// } // for (inboxValue, inboxUserId) in inboxUsers.enumerated() {
// .onEnded {_ in ForEach(0..<inboxUsers.count, id: \.self) {inboxValue in
// self.recordingGestureDeactived() let inboxUserId = inboxUsers[inboxValue]
// } let adjustedValue = inboxValue + activeFriends.count
// ) GeometryReader {gridProxy in
// IMPORTANT: accounting for the active friends iterations
let scale = getScale(proxy: gridProxy, itemNumber: adjustedValue, userId: inboxUserId)
ZStack(alignment: .topLeading) {
// check if the last message in the conversation between me and my friend was me talking or him
// also check if I have listened to it once or twice
if self.haveNewMessageFromFriend(friendDbId: inboxUserId) { // him talking
Image(systemName: "wave.3.right.circle.fill")
.foregroundColor(Color.orange)
.font(.title)
}
Circle()
.foregroundColor(self.getBubbleTint(friendIndex: adjustedValue, friendDbId: inboxUserId)) // different color for a selected user
.blur(radius: 8)
.cornerRadius(100)
Image(self.authSessionStore.relevantUsersDict[inboxUserId]?.avatar ?? Avatars.avatarSystemNames[0])
.resizable()
.scaledToFit()
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 20)
}
.scaleEffect(scale)
.padding(scale * 5)
} // geometry reader
.offset(
x: honeycombOffSetX(adjustedValue),
y: 0
)
.id(inboxUserId) // id for scrollviewreader
.frame(height: Self.size)
.onTapGesture {
// action to open alert to add this person to circle or reject
// create user_friend with isActive = false
}
.animation(Animation.spring()) .animation(Animation.spring())
} }
} // TODO: add padding based on if we are on any cornering item to allow the bubble to enlargen } // TODO: add padding based on if we are on any cornering item to allow the bubble to enlargen
@@ -161,16 +194,19 @@ struct CircleGridView: View {
//TODO: was causing problems so commenting out //TODO: was causing problems so commenting out
//may not need anymore with bottom nav activation //may not need anymore with bottom nav activation
// scrollReaderValue.scrollTo(Self.numberOfItems / 2) // scrollReaderValue.scrollTo(Self.numberOfItems / 2)
} }
} // scrollview reader } // scrollview reader
} }
private func haveNewMessageFromFriend(friendDbId: String) -> Bool { private func haveNewMessageFromFriend(friendDbId: String) -> Bool {
if self.authSessionStore.user != nil { if self.authSessionStore.user != nil {
let userId = self.authSessionStore.user!.id // O(1) // currUser who is signed in let userId = self.authSessionStore.user!.id // O(1) // currUser who is signed in
// get most recent message in the convo and see who has spoken // get most recent message in the convo and see who has spoken
if let messagesRelatedToFriend = self.authSessionStore.friendMessagesDict[friendDbId] { // O(1) if let messagesRelatedToFriend = self.authSessionStore.relevantMessagesByUserDict[friendDbId] { // O(1)
return messagesRelatedToFriend.first?.receiverId == userId return messagesRelatedToFriend.first?.receiverId == userId
} }
} }
@@ -179,7 +215,7 @@ struct CircleGridView: View {
} }
private func getBubbleTint(friendIndex: Int, friendDbId: String) -> Color { private func getBubbleTint(friendIndex: Int, friendDbId: String) -> Color {
if (friendIndex == self.selectedFriendIndex) { // user clicked on this user if (friendDbId == self.selectedFriendIndex) { // user clicked on this user
return NirvanaColor.dimTeal.opacity(0.4) return NirvanaColor.dimTeal.opacity(0.4)
} }
else if self.haveNewMessageFromFriend(friendDbId: friendDbId) { // this user has a message else if self.haveNewMessageFromFriend(friendDbId: friendDbId) { // this user has a message
@@ -202,9 +238,9 @@ struct CircleGridView_Previews: PreviewProvider {
extension CircleGridView { extension CircleGridView {
// getting the proxy of an individual item // getting the proxy of an individual item
// and decoding into a scale that the item should take // and decoding into a scale that the item should take
private func getScale(proxy: GeometryProxy, itemNumber: Int) -> CGFloat { private func getScale(proxy: GeometryProxy, itemNumber: Int, userId: String) -> CGFloat {
// if this user is selected // if this user is selected
if itemNumber == self.selectedFriendIndex { if userId == self.selectedFriendIndex {
return big + 0.2 return big + 0.2
} }
@@ -269,18 +305,18 @@ extension CircleGridView {
// extension for handling the gestures and actions // extension for handling the gestures and actions
extension CircleGridView { extension CircleGridView {
// listening to messages // listening to messages
private func handleTap(friendIndex: Int, friend: User) { private func handleTap(gridItemIndex: Int, friendId: String) {
print("tap gesture activated") print("tap gesture activated")
// clearing the player to make room for this friend's convo or to deselect this user // clearing the player to make room for this friend's convo or to deselect this user
self.queuePlayer.removeAllItems() self.queuePlayer.removeAllItems()
// if user had previously selected user, put nil as a toggle // if user had previously selected user, put nil as a toggle
if self.selectedFriendIndex == friendIndex { if self.selectedFriendIndex == friendId {
self.selectedFriendIndex = nil self.selectedFriendIndex = nil
return return
} else { } else {
self.selectedFriendIndex = friendIndex self.selectedFriendIndex = friendId
} }
// TODO: OPTIMIZATION...buffer and load all AVAssets to create AVPlayerItems before a tap happens...but this can also cause load in background if user is not playing a message right now...this isn't an optimization of the data/firestore but rather the player // TODO: OPTIMIZATION...buffer and load all AVAssets to create AVPlayerItems before a tap happens...but this can also cause load in background if user is not playing a message right now...this isn't an optimization of the data/firestore but rather the player
@@ -293,7 +329,7 @@ extension CircleGridView {
// traverse through reversed list of messages and add to audio player queue // traverse through reversed list of messages and add to audio player queue
// TODO: protect against force unwraps // TODO: protect against force unwraps
var AVPlayerItems: [AVPlayerItem] = [] var AVPlayerItems: [AVPlayerItem] = []
let messagesRelatedToFriend = self.authSessionStore.friendMessagesDict[friend.id!] ?? [] let messagesRelatedToFriend = self.authSessionStore.relevantMessagesByUserDict[friendId] ?? []
if messagesRelatedToFriend.count == 0 { if messagesRelatedToFriend.count == 0 {
return return
@@ -13,7 +13,7 @@ struct InnerCircleView: View {
@EnvironmentObject var authSessionStore: AuthSessionStore @EnvironmentObject var authSessionStore: AuthSessionStore
@EnvironmentObject var navigationStack: NavigationStack @EnvironmentObject var navigationStack: NavigationStack
@State var selectedFriendIndex: Int? = nil @State var selectedFriendIndex: String? = nil
let universalSize = UIScreen.main.bounds let universalSize = UIScreen.main.bounds
@@ -56,7 +56,7 @@ struct InnerCircleView: View {
} }
.padding() .padding()
} }
else if self.authSessionStore.friendsArr.count == 0 { else if self.authSessionStore.getActiveFriendIds().count == 0 && self.authSessionStore.getInboxUsersIds().count == 0 {
VStack(alignment: .center) { VStack(alignment: .center) {
Image("undraw_fall_is_coming_yl-0-x") Image("undraw_fall_is_coming_yl-0-x")
.renderingMode(.original) .renderingMode(.original)
@@ -109,7 +109,7 @@ struct InnerCircleView: View {
// helper for new users // helper for new users
// TODO: make it back to 1 instead of 10...testing // TODO: make it back to 1 instead of 10...testing
if self.authSessionStore.friendsArr.count == 1 && self.selectedFriendIndex == nil { // don't show if bottom metadata showing if self.authSessionStore.getActiveFriendIds().count == 1 && self.selectedFriendIndex == nil { // don't show if bottom metadata showing
ZStack(alignment: .bottomTrailing) { ZStack(alignment: .bottomTrailing) {
Color.clear Color.clear