diff --git a/nirvana-ios/Views/Contacts/ContactsViewModel.swift b/nirvana-ios/Views/Contacts/ContactsViewModel.swift index 429208d..680f6b2 100644 --- a/nirvana-ios/Views/Contacts/ContactsViewModel.swift +++ b/nirvana-ios/Views/Contacts/ContactsViewModel.swift @@ -8,8 +8,45 @@ import Foundation import Contacts import SwiftUI +import AlertToast class ContactsViewModel : ObservableObject { + @Published var toast: Toast? { + didSet { + self.showToast = true + } + } + @Published var showToast: Bool = false + + enum Toast: Identifiable { + var id: Self { self } + + case maxFriendsInCircle + case cannotFriendYourself + case addedFriend + case removedFriend + case fetchingContacts + + case generalError + + var view: AlertToast { + switch self { + case .fetchingContacts: + return AlertToast(displayMode: .hud, type: .systemImage("person.3.fill", NirvanaColor.dimTeal), title: "looking for friends") + case .removedFriend: + return AlertToast(displayMode: .hud, type: .complete(Color.green), title: "removed friend") + case .addedFriend: + return AlertToast(displayMode: .hud, type: .complete(Color.green), title: "added friend") + case .cannotFriendYourself: + return AlertToast(displayMode: .hud, type: .error(Color.orange), title: "silly! 🙉", subTitle: "you cannot friend yourself") + case .maxFriendsInCircle: + return AlertToast(displayMode: .hud, type: .systemImage("person.crop.circle.badge.exclamationmark.fill", Color.orange), title: "circle full!", subTitle: "tap on an existing friend and hold down on their icon in the bottom left to remove them to make space") + default: + return AlertToast(displayMode: .hud, type: .error(Color.orange), title: "Something went wrong ‼️") + } + } + } + @Published var showPermissionAlert = false @Published var contacts: [String: ContactsViewModelContact] = [:] @@ -48,6 +85,8 @@ class ContactsViewModel : ObservableObject { fetchRequest.sortOrder = .userDefault do { + self.toast = .fetchingContacts + try store.enumerateContacts(with: fetchRequest) {[weak self](contact, stop) in // only checking if american number or not for now var cnPhoneNumber = contact.phoneNumbers.first?.value.stringValue @@ -88,7 +127,6 @@ class ContactsViewModel : ObservableObject { } } catch { print("Unable to fetch contacts. \(error)") - } } @@ -97,6 +135,7 @@ class ContactsViewModel : ObservableObject { // make sure userId is not the same as friendId...don't want people friending themselves if userId == friendId { completion(ServiceState.error(ServiceError(description: "You cannot friend yourself, silly!"))) + self.toast = .cannotFriendYourself return } diff --git a/nirvana-ios/Views/Contacts/FindFriendsView.swift b/nirvana-ios/Views/Contacts/FindFriendsView.swift index 44e7d7d..2e4a3b8 100644 --- a/nirvana-ios/Views/Contacts/FindFriendsView.swift +++ b/nirvana-ios/Views/Contacts/FindFriendsView.swift @@ -8,6 +8,7 @@ import SwiftUI import Contacts import NavigationStack +import AlertToast struct FindFriendsView: View { @EnvironmentObject var navigationStack : NavigationStack @@ -52,6 +53,9 @@ struct FindFriendsView: View { .onAppear { self.contactsVM.fetchContacts() } + .toast(isPresenting: self.$contactsVM.showToast) { + self.contactsVM.toast?.view ?? AlertToast(displayMode: .hud, type: .error(Color.red), title: "Something went wrong") + } } var searchItems: [String] { @@ -134,24 +138,28 @@ struct ListContactRow: View { primaryButton: .default(Text("Cancel")), secondaryButton: .default(Text("Confirm")) { print("adding contact to circle") - // call method in vm to get it done, then navigate to the circle - if self.authSessionStore.friendsArr.count < 10 || self.authSessionStore.user?.phoneNumber == "+19499230445" { - self.contactsVM.addOrActivateFriendToCircle(userId: self.authSessionStore.user!.id!, friendId: (contact.user!.id)!) {res in - print(res) - - switch res { - case .error(let err): - print(err) - case .success(let str): - print(str) - } - } + if self.authSessionStore.friendsArr.count >= 10 { + self.contactsVM.toast = .maxFriendsInCircle + return + } + + self.contactsVM.addOrActivateFriendToCircle(userId: self.authSessionStore.user!.id!, friendId: (contact.user!.id)!) {res in + print(res) - self.navigationStack.push(InnerCircleView()) + switch res { + case .error(let err): + print(err) + self.contactsVM.toast = .generalError + case .success(let str): + print(str) + self.contactsVM.toast = .addedFriend + self.navigationStack.push(InnerCircleView()) + } } } ) } + } else { // invite button to text the person HStack(alignment: .center, spacing: 0) { diff --git a/nirvana-ios/Views/InnerCircle/CircleFooterView.swift b/nirvana-ios/Views/InnerCircle/CircleFooterView.swift index 4202dd9..a40d25a 100644 --- a/nirvana-ios/Views/InnerCircle/CircleFooterView.swift +++ b/nirvana-ios/Views/InnerCircle/CircleFooterView.swift @@ -43,11 +43,9 @@ struct CircleFooterView: View { Button(role: .destructive) { print("deactivating friend...removing from circle") if self.authSessionStore.user?.id != nil && self.selectedFriendIndex != nil { - self.innerCircleVM.activateOrDeactiveInboxUser(activate: false, userId: self.authSessionStore.user!.id!, friendId: self.selectedFriendIndex!) {res in - - print(res) - self.selectedFriendIndex = nil // making this footer disappear although a view change should do this - } + self.innerCircleVM.activateOrDeactiveInboxUser(activate: false, userId: self.authSessionStore.user!.id!, friendId: self.selectedFriendIndex!) + + self.selectedFriendIndex = nil } } label: { Label("Remove from Circle", systemImage: "person.crop.circle.fill.badge.minus") diff --git a/nirvana-ios/Views/InnerCircle/CircleGridView.swift b/nirvana-ios/Views/InnerCircle/CircleGridView.swift index 20ca75b..8d60f7b 100644 --- a/nirvana-ios/Views/InnerCircle/CircleGridView.swift +++ b/nirvana-ios/Views/InnerCircle/CircleGridView.swift @@ -131,7 +131,6 @@ struct CircleGridView: View { else { print("no convo id to join") } - } else { self.convoVM.toast = .alreadyInCall @@ -308,17 +307,17 @@ struct CircleGridView: View { message: Text(self.alertSubtext), primaryButton: .destructive(Text("Reject"), action: { // create user friend but a rejected one - if self.authSessionStore.friendsArr.count < 10 || self.authSessionStore.user?.phoneNumber == "+19499230445" { - self.innerCircleVM.activateOrDeactiveInboxUser(activate: false, userId: self.authSessionStore.user!.id!, friendId: inboxUserId) { res in - print(res) - } - } + self.innerCircleVM.activateOrDeactiveInboxUser(activate: false, userId: self.authSessionStore.user!.id!, friendId: inboxUserId) }), secondaryButton: .default(Text("Add"), action: { - // create user friend - self.innerCircleVM.activateOrDeactiveInboxUser(activate: true, userId: self.authSessionStore.user!.id!, friendId: inboxUserId) { res in - print(res) + // create user friend if have space in circle + // TODO: view models should be able to do this validation, need some way of view models to speak to each other + if self.authSessionStore.friendsArr.count >= 10 { + self.innerCircleVM.toast = .maxFriendsInCircle + return } + + self.innerCircleVM.activateOrDeactiveInboxUser(activate: true, userId: self.authSessionStore.user!.id!, friendId: inboxUserId) }) ) } diff --git a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift index 492970f..3cc4a0a 100644 --- a/nirvana-ios/Views/InnerCircle/InnerCircleView.swift +++ b/nirvana-ios/Views/InnerCircle/InnerCircleView.swift @@ -153,6 +153,9 @@ struct InnerCircleView: View { .toast(isPresenting: self.$convoViewModel.showToast) { self.convoViewModel.toast?.view ?? AlertToast(displayMode: .hud, type: .error(Color.red), title: "Something went wrong") } + .toast(isPresenting: self.$innerCircleVM.showToast) { + self.innerCircleVM.toast?.view ?? AlertToast(displayMode: .hud, type: .error(Color.red), title: "Something went wrong") + } // .onDisappear { // print("deiniting data listeners, but current data should still be cached!") // diff --git a/nirvana-ios/Views/InnerCircle/InnerCircleViewModel.swift b/nirvana-ios/Views/InnerCircle/InnerCircleViewModel.swift index 5ddcd9f..f57b992 100644 --- a/nirvana-ios/Views/InnerCircle/InnerCircleViewModel.swift +++ b/nirvana-ios/Views/InnerCircle/InnerCircleViewModel.swift @@ -7,8 +7,58 @@ import Foundation import AVFoundation +import AlertToast +import SwiftUI class InnerCircleViewModel: ObservableObject { + @Published var toast: Toast? { + didSet { + self.showToast = true + } + } + @Published var showToast: Bool = false + + enum Toast: Identifiable { + var id: Self { self } + + case startedClip + case problemSendingClip + case nothingRecorded + case clipSent + + case maxFriendsInCircle + case cannotFriendYourself + case addedFriend + case removedFriend + + case generalError + + + var view: AlertToast { + switch self { + case .removedFriend: + return AlertToast(displayMode: .hud, type: .complete(Color.green), title: "removed friend") + case .addedFriend: + return AlertToast(displayMode: .hud, type: .complete(Color.green), title: "added friend") + case .cannotFriendYourself: + return AlertToast(displayMode: .hud, type: .error(Color.orange), title: "silly! 🙉", subTitle: "you cannot friend yourself") + case .maxFriendsInCircle: + return AlertToast(displayMode: .hud, type: .systemImage("person.crop.circle.badge.exclamationmark.fill", Color.orange), title: "circle full!", subTitle: "tap on an existing friend and hold down on their icon in the bottom left to remove them to make space") + case .nothingRecorded: + return AlertToast(displayMode: .hud, type: .systemImage("exclamationmark.triangle.fill", NirvanaColor.teal), title: "nothing recorded", subTitle: "please try again") + case .clipSent: + return AlertToast(displayMode: .hud, type: .systemImage("paperplane.circle.fill", NirvanaColor.teal), title: "clip sent") + case .problemSendingClip: + return AlertToast(displayMode: .hud, type: .error(Color.orange), title: "problem sending clip", subTitle: "please try again") + case .startedClip: + return AlertToast(displayMode: .hud, type: .systemImage("waveform.circle.fill", NirvanaColor.teal), title: "clip started") + default: + return AlertToast(displayMode: .hud, type: .error(Color.orange), title: "Something went wrong ‼️") + } + } + } + + var audioRecorder : AVAudioRecorder! var audioPlayer : AVAudioPlayer! @@ -21,7 +71,7 @@ class InnerCircleViewModel: ObservableObject { private let cloudStorageService = CloudStorageService() private let firestoreService = FirestoreService() private let pushNotificationService = PushNotificationService() - private let agoraService = AgoraService() + init() { // separate set up for listening vs recording @@ -57,6 +107,8 @@ extension InnerCircleViewModel { audioRecorder.record() isRecording = true + self.toast = .startedClip + self.audioLocalUrl = filePath // setting this for later use when recording is stopped } catch { @@ -81,6 +133,7 @@ extension InnerCircleViewModel { self.cloudStorageService.uploadLocalUrl(localFileUrl: self.audioLocalUrl!) {[weak self] audioDataUrl in if audioDataUrl == nil { print("there was an error in uploading file") + self?.toast = .problemSendingClip return } @@ -90,18 +143,33 @@ extension InnerCircleViewModel { self?.firestoreService.createMessage(message: newMessage) {[weak self] res in print(res) - // sending push notification if there was a device token - if receiver.deviceToken != nil && receiver.nickname != nil { - self?.pushNotificationService.sendPushNotification(to: receiver.deviceToken!, title: "🌱Nirvana", body: "continue your conversation with \(sender.nickname ?? "your friend")") + switch res { + case .success: + + self?.toast = .clipSent + + // sending push notification if there was a device token for this friend + if receiver.deviceToken != nil && receiver.nickname != nil { + self?.pushNotificationService.sendPushNotification(to: receiver.deviceToken!, title: "🌱Nirvana", body: "continue your conversation with \(sender.nickname ?? "your friend")") + } + + // delete local audio file from user's phone so that it doesn't take crazy space + print("stopped recording: file about to get deleted from \(self?.getTemporaryDirectory()) with filename: \(self?.audioLocalUrl)") + + try? FileManager.default.removeItem(at: (self?.audioLocalUrl)!) + case .error(let err): + print(err) + self?.toast = .problemSendingClip + default: + self?.toast = .problemSendingClip } - - // delete local audio file from user's phone so that it doesn't get crazy - print("stopped recording: file about to get deleted from \(self?.getTemporaryDirectory()) with filename: \(self?.audioLocalUrl)") - - try? FileManager.default.removeItem(at: (self?.audioLocalUrl)!) } } } + else { + print("nothing recorded, can't send") + self.toast = .problemSendingClip + } } func getTemporaryDirectory() -> URL { @@ -123,11 +191,12 @@ extension InnerCircleViewModel { // handle activating or deactivating friends extension InnerCircleViewModel { - func activateOrDeactiveInboxUser(activate: Bool, userId: String, friendId: String, completion: @escaping((_ state: ServiceState) -> ())) { + func activateOrDeactiveInboxUser(activate: Bool, userId: String, friendId: String) { // validation // make sure userId is not the same as friendId...don't want people friending themselves if userId == friendId { - completion(ServiceState.error(ServiceError(description: "You cannot friend yourself, silly!"))) + print("you cannot friend yourself") + self.toast = .cannotFriendYourself return } @@ -135,7 +204,20 @@ extension InnerCircleViewModel { var userFriend = UserFriends(userId: userId, friendId: friendId, isActive: activate, lastUpdatedTimestamp: nil) self.firestoreService.createOrUpdateUserFriends(userFriend: userFriend, activateOrDeactivate: activate) {[weak self] res in - completion(res) + switch res { + case .success: + if activate { + self?.toast = .addedFriend + } + else { + self?.toast = .removedFriend + } + case .error(let err): + print(err) + self?.toast = .generalError + default: + self?.toast = .generalError + } } } } @@ -146,15 +228,3 @@ extension InnerCircleViewModel { } } - -// everything related to calls and such -extension InnerCircleViewModel { - func getAgoraToken() { - if let uid = AuthSessionStore.getCurrentUserId() { - self.agoraService.getAgoraUserTokenServer(channelName: uid) - } - else { - print("no user authenticated to get an agora token") - } - } -}