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
+37 -61
View File
@@ -33,13 +33,12 @@ final class AuthSessionStore: ObservableObject, SessionStore {
@Published var sessionState: SessionState = SessionState.notCheckedYet
// TODO: figure out which ones to publish
@Published var friendsArr: [User] = [] // all active and inactive friends
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
@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 inboxUsers: [User] = []
private var dataListeners: [ListenerRegistration] = []
private var listenersActive = false // false on app/processes killed
@@ -245,8 +244,8 @@ extension AuthSessionStore {
}
// resetting array to reset friends
self.friendsArr.removeAll()
self.userFriends.removeAll()
self.relevantUsersDict.removeAll()
self.userFriendsDict.removeAll()
for document in querySnapshot!.documents {
let userFriend:UserFriends? = try? document.data(as: UserFriends.self)
@@ -255,15 +254,16 @@ extension AuthSessionStore {
continue
}
self.userFriends.append(userFriend!)
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)
DispatchQueue.main.async {
if returnedUser != nil {
self.friendsArr.append(returnedUser!)
self.relevantUsersDict[userFriend!.friendId] = returnedUser!
self.objectWillChange.send()
@@ -318,7 +318,6 @@ extension AuthSessionStore {
// clearing dict to allow clean list of messages to be put forth
//optimize this? but also saving on memory and same db reads
self.relevantMessagesByUserDict.removeAll()
self.inboxUsers.removeAll()
self.messagesArr = documents.compactMap { (queryDocumentSnapshot) -> Message? in
do {
@@ -329,7 +328,7 @@ extension AuthSessionStore {
if currMessage != nil { // not really possible but just check
// 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
// 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
if currMessage!.senderId == currUserId { // if I am the sender
if self.relevantMessagesByUserDict[currMessage!.receiverId] == nil {
@@ -345,6 +344,21 @@ extension AuthSessionStore {
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!
}
}
}
}
@@ -356,11 +370,7 @@ extension AuthSessionStore {
print(error)
}
return nil
}
// TODO: concurrency problem if the friends array is not filled? prolly not
// get all inbox users' data
self.getIncomingNewMessageUserIds()
}
}
self.listenersActive = true
@@ -387,55 +397,21 @@ extension AuthSessionStore {
// transforming data to make it more valuable
extension AuthSessionStore {
func getActiveFriends() -> [User] {
let activeFriendRelationships = self.userFriends.filter {userFriend in
return userFriend.isActive
}
let activeFriendsIds = activeFriendRelationships.map {activeUserFriend in
return activeUserFriend.id
}
func getActiveFriendIds() -> [String] {
var activeFriendIds: [String] = []
// they should already be in the friends arr
return self.friendsArr.filter {friend in
return activeFriendsIds.contains(friend.id)
}
}
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!)
for (friendId, userFriend) in self.userFriendsDict {
if userFriend.isActive {
activeFriendIds.append(friendId)
}
}
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()
// checking if currcontact is already a friend
let currFriends = self.authSessionStore.friendsArr.map { $0.id }
let newPotentialFriends = keys.filter {
if let friend = self.contactsVM.contacts[$0]?.user { // seeing if this contact is an existing user
if currFriends.contains(friend.id) { // if this is already a friend
let activeFriendIds = self.authSessionStore.getActiveFriendIds()
let newPotentialFriends = keys.filter {contactSortProp in
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
}
@@ -12,7 +12,7 @@ struct CircleFooterView: View {
@EnvironmentObject var navigationStack: NavigationStack
@EnvironmentObject var authSessionStore: AuthSessionStore
@Binding var selectedFriendIndex: Int?
@Binding var selectedFriendIndex: String?
@State private var convoRelativeTime = ""
@State private var selectedFriend: User?
@@ -94,7 +94,7 @@ struct CircleFooterView: View {
// set convo moment/relative time to show
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
let lastMessage = self.authSessionStore.relevantMessagesByUserDict[self.selectedFriend!.id!]?.first
@@ -19,25 +19,21 @@ struct CircleGridView: View {
let universalSize = UIScreen.main.bounds
var body: some View {
gridContent
}
// 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
@State 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
@Binding var selectedFriendIndex: Int?
private static let totalColumns: Int = 3 // scaling the circles and calculating column count
// 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)
@Binding var selectedFriendIndex: String?
private let big:CGFloat = 1
private let medium:CGFloat = 0.75
private let small:CGFloat = 0.5
@@ -49,10 +45,12 @@ struct CircleGridView: View {
let longPressMinDuration = 0.5
private var gridContent: some View {
var body: some View {
// main communication hub
// TODO: client side, sort the honeycomb from top left to bottom right
// based on most close friends to least
let activeFriends = self.authSessionStore.getActiveFriendIds()
let inboxUsers = self.authSessionStore.getInboxUsersIds()
ScrollViewReader {scrollReaderValue in
ScrollView([.horizontal, .vertical], showsIndicators: false) {
LazyVGrid(
@@ -60,29 +58,27 @@ struct CircleGridView: View {
alignment: .center,
spacing: Self.spacingBetweenRows
) {
ForEach(Array(self.authSessionStore.friendsArr.enumerated()), id: \.offset) { value, friend in
// for (value, element) in self.authSessionStore.friendMessagesDict.keys.enumerated() {
// active friends
ForEach(0..<activeFriends.count, id: \.self) {value in
let friendId = activeFriends[value]
GeometryReader {gridProxy in
let scale = getScale(proxy: gridProxy, itemNumber: value)
// TODO: sort the list but may already be sorted from the query and creation of the array of messages?
// shouldn't be nil...hopefully
let scale = getScale(proxy: gridProxy, itemNumber: value, userId: friendId)
ZStack(alignment: .topTrailing) {
// 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: friend.id!) { // him talking
if self.haveNewMessageFromFriend(friendDbId: friendId) { // him talking
Image(systemName: "wave.3.right.circle.fill")
.foregroundColor(Color.orange)
.font(.title)
}
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)
.cornerRadius(100)
Image(friend.avatar ?? Avatars.avatarSystemNames[0])
Image(self.authSessionStore.relevantUsersDict[friendId]?.avatar ?? Avatars.avatarSystemNames[0])
.resizable()
.scaledToFit()
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 20)
@@ -94,10 +90,10 @@ struct CircleGridView: View {
x: honeycombOffSetX(value),
y: 0
)
.id(value) // id for scrollviewreader
.id(friendId) // id for scrollviewreader
.frame(height: Self.size)
.onTapGesture {
self.handleTap(friendIndex: value, friend: friend)
self.handleTap(gridItemIndex: value, friendId: friendId)
}// TODO: maybe add simulataneous gesture or sequence? with the tap gesture?
.gesture(
LongPressGesture(minimumDuration: longPressMinDuration)
@@ -106,7 +102,7 @@ struct CircleGridView: View {
self.queuePlayer.removeAllItems()
print("activated long press!")
self.selectedFriendIndex = value
self.selectedFriendIndex = friendId
self.activateHaptics()
@@ -137,20 +133,57 @@ struct CircleGridView: View {
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()
}
)
// .simultaneousGesture(
// DragGesture(minimumDistance: 0, coordinateSpace: .local)
// .onChanged {_ in
// self.recordingGestureActive(friendIndex: value, friend: friend)
// }
// .onEnded {_ in
// self.recordingGestureDeactived()
// }
// )
.animation(Animation.spring())
}
// ForEach(Array(self.authSessionStore.getInboxUsersIds.enumerated()), id: \.offset) { inboxValue, inboxUser in
// for (inboxValue, inboxUserId) in inboxUsers.enumerated() {
ForEach(0..<inboxUsers.count, id: \.self) {inboxValue in
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())
}
} // 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
//may not need anymore with bottom nav activation
// scrollReaderValue.scrollTo(Self.numberOfItems / 2)
}
} // scrollview reader
}
private func haveNewMessageFromFriend(friendDbId: String) -> Bool {
if self.authSessionStore.user != nil {
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
if let messagesRelatedToFriend = self.authSessionStore.friendMessagesDict[friendDbId] { // O(1)
if let messagesRelatedToFriend = self.authSessionStore.relevantMessagesByUserDict[friendDbId] { // O(1)
return messagesRelatedToFriend.first?.receiverId == userId
}
}
@@ -179,7 +215,7 @@ struct CircleGridView: View {
}
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)
}
else if self.haveNewMessageFromFriend(friendDbId: friendDbId) { // this user has a message
@@ -202,9 +238,9 @@ struct CircleGridView_Previews: PreviewProvider {
extension CircleGridView {
// 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 {
private func getScale(proxy: GeometryProxy, itemNumber: Int, userId: String) -> CGFloat {
// if this user is selected
if itemNumber == self.selectedFriendIndex {
if userId == self.selectedFriendIndex {
return big + 0.2
}
@@ -269,18 +305,18 @@ extension CircleGridView {
// extension for handling the gestures and actions
extension CircleGridView {
// listening to messages
private func handleTap(friendIndex: Int, friend: User) {
private func handleTap(gridItemIndex: Int, friendId: String) {
print("tap gesture activated")
// clearing the player to make room for this friend's convo or to deselect this user
self.queuePlayer.removeAllItems()
// if user had previously selected user, put nil as a toggle
if self.selectedFriendIndex == friendIndex {
if self.selectedFriendIndex == friendId {
self.selectedFriendIndex = nil
return
} 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
@@ -293,7 +329,7 @@ extension CircleGridView {
// traverse through reversed list of messages and add to audio player queue
// TODO: protect against force unwraps
var AVPlayerItems: [AVPlayerItem] = []
let messagesRelatedToFriend = self.authSessionStore.friendMessagesDict[friend.id!] ?? []
let messagesRelatedToFriend = self.authSessionStore.relevantMessagesByUserDict[friendId] ?? []
if messagesRelatedToFriend.count == 0 {
return
@@ -13,7 +13,7 @@ struct InnerCircleView: View {
@EnvironmentObject var authSessionStore: AuthSessionStore
@EnvironmentObject var navigationStack: NavigationStack
@State var selectedFriendIndex: Int? = nil
@State var selectedFriendIndex: String? = nil
let universalSize = UIScreen.main.bounds
@@ -56,7 +56,7 @@ struct InnerCircleView: View {
}
.padding()
}
else if self.authSessionStore.friendsArr.count == 0 {
else if self.authSessionStore.getActiveFriendIds().count == 0 && self.authSessionStore.getInboxUsersIds().count == 0 {
VStack(alignment: .center) {
Image("undraw_fall_is_coming_yl-0-x")
.renderingMode(.original)
@@ -109,7 +109,7 @@ struct InnerCircleView: View {
// helper for new users
// 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) {
Color.clear