From 70823e4c9daf9abce92e08dff6554526bf839f10 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Thu, 21 Mar 2024 22:38:23 -0700 Subject: [PATCH 01/20] Updated the database code for detecting changes in transaction --- .../ViewModels/DatabaseAPI.swift | 145 ++++++++++++------ 1 file changed, 95 insertions(+), 50 deletions(-) diff --git a/reciept bill splitter/ViewModels/DatabaseAPI.swift b/reciept bill splitter/ViewModels/DatabaseAPI.swift index 1a4d9e5..2254781 100644 --- a/reciept bill splitter/ViewModels/DatabaseAPI.swift +++ b/reciept bill splitter/ViewModels/DatabaseAPI.swift @@ -336,6 +336,100 @@ class DatabaseAPI { return nil } + static func assignAllGroupMembersPayment(transaction_id: String) async -> Void { + guard let _ = Auth.auth().currentUser else { + print("User Does not exist") + return + } + + let transactionRef = db.collection("transactions").document(transaction_id) + // Add New AssignedTransaction for transaction in user + do { + let document = try await transactionRef.getDocument() + + guard let transactionData = document.data() else { + return + } + + let groupId = transactionData["group_id"] as? String ?? "" + let groupRef = db.collection("groups").document(groupId) + let groupDocument = try await groupRef.getDocument() + + guard let groupData = groupDocument.data() else { + return + } + + let groupMembers = groupData["members"] as? [String] ?? [] + + // Read document and work + do { + // Firestore Transaction to ensure both documents are written together or both fail + let _ = try await db.runTransaction({ (transaction, errorPointer) -> Any? in + // LOOP through transactions and create a new assigned transaction for each user + let itemBidders = transactionData["itemBidders"] as? [String:[String]] ?? [:] + let items = transactionData["items"] as? [[String : Any]] ?? [[:]] + + var newItemList: [Item] = [] + for item in items { + let newItem = Item(priceInCents: item["priceInCents"] as? Int ?? 0, name: item["name"] as? String ?? "Unknown Item") + newItemList.append(newItem) + } + + // An Abomination of Code + for groupMember in groupMembers { + let userReference = db.collection("users").document(groupMember) + let userDocument: DocumentSnapshot + + do { + try userDocument = transaction.getDocument(userReference) + } catch let fetchError as NSError { + errorPointer?.pointee = fetchError + return nil + } + + var totalCostToPay: Float = 0 + // Check every item for user + for (index, item) in newItemList.enumerated() { + // Seach For Member in item bids + let currentIndex = String(index) + let currentItemBidders = itemBidders[currentIndex] ?? [] + + for userId in currentItemBidders { + if userId == groupMember { + // Add Total cost to pay for user + totalCostToPay += Float(item.priceInCents) / Float(currentItemBidders.count) + } + } + } + // After Adding cost for user for all items + // Create AssignedTransaction for User + var assignedTransactionDict: [String:Any] = [:] + assignedTransactionDict["transactionName"] = transactionData["name"] as? String ?? "" + assignedTransactionDict["associatedTransaction_id"] = transaction_id + assignedTransactionDict["user_idToPay"] = groupData["owner_id"] as? String ?? "" + assignedTransactionDict["isPaid"] = false + assignedTransactionDict["ammountToPay"] = totalCostToPay + + transaction.updateData(["assignedTransaction.\(transaction_id)": assignedTransactionDict], forDocument: userReference) + } + print("FINISHED") + return nil + }) + + return + } catch { + // Handle error during transaction + print("Error Assigning Transaction: \(error)") + return + } + + } catch let error { + print("Error updating transaction: \(error)") + } + + + } + static func retrieveStripeCustomerId(uid: String, completion: @escaping (String?) -> Void) { let db = Firestore.firestore() let customerRef = db.collection("customers").document(uid) @@ -413,56 +507,7 @@ class DatabaseAPI { } } - - static func assignAllGroupMembersPayment(transaction_id: String) async -> Void { - guard let _ = Auth.auth().currentUser else { - print("User Does not exist") - return - } - - let transactionRef = db.collection("transactions").document(transaction_id) - // Add New AssignedTransaction for transaction in user - do { - let document = try await transactionRef.getDocument() - - if !document.exists { - return - } - - // Read document and work - do { - // Firestore Transaction to ensure both documents are written together or both fail - let _ = try await db.runTransaction({ (transaction, errorPointer) -> Any? in - // LOOP through transactions and create a new assigned transaction for each user - // let gDoc: DocumentSnapshot - // do { - // try gDoc = transaction.getDocument(docRef) - // } catch let fetchError as NSError { - // errorPointer?.pointee = fetchError - // return nil - // } - // - // // Add user id to group members - // transaction.updateData(["members": FieldValue.arrayUnion([user.uid])], forDocument: gDoc.reference) - // // Add group id to user groups - // transaction.updateData(["groups": FieldValue.arrayUnion([gDoc.documentID])], forDocument: userRef) - return nil - }) - - return - } catch { - // Handle error during transaction - print("Error Assigning Transaction: \(error)") - return - } - - } catch let error { - print("Error updating transaction: \(error)") - } - - - } - + static func fetchUsernames(for documentIDs: [String], completion: @escaping (Result<[String], Error>) -> Void) { let userCollection = db.collection("users") From d69c608491f3b5d08001b9db8918c1ec6966bc5c Mon Sep 17 00:00:00 2001 From: diegomtz5 Date: Thu, 21 Mar 2024 22:39:00 -0700 Subject: [PATCH 02/20] idl --- reciept bill splitter/GroupDetailView.swift | 2 +- reciept bill splitter/HomeView.swift | 16 ++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/reciept bill splitter/GroupDetailView.swift b/reciept bill splitter/GroupDetailView.swift index 366a6c6..4454eda 100644 --- a/reciept bill splitter/GroupDetailView.swift +++ b/reciept bill splitter/GroupDetailView.swift @@ -83,7 +83,7 @@ struct GroupDetailView: View { Task { await createTransaction() } - //isCameraPresented = true + isCameraPresented = true } .sheet(isPresented: $isCameraPresented) { CameraView(isPresented: $isCameraPresented, selectedImage: $selectedImage, isTaken: $isTaken) diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index 288cb72..fac4c6b 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -9,7 +9,6 @@ struct HomeView: View { @State private var isJoiningGroup = false @State private var isEmptyDisplayFormat = true @EnvironmentObject var userViewModel: UserViewModel - @State private var isCreatingGroup = false @State private var selectedGroup: Group? @State private var isAlert = false @@ -43,7 +42,6 @@ struct HomeView: View { Button("Transfer Money") { paymentManager.transferMoney(amount: 1000, destinationAccountId: "acct_1Ovoc6QQyo8likZn") - } Button("Collect Payment") { @@ -83,16 +81,11 @@ struct HomeView: View { } else { showInfoAlert = true } - }.foregroundColor(userViewModel.canGetPaid ? .white : .red) // Text color changes based on `canGetPaid` - - + }.foregroundColor(userViewModel.canGetPaid ? .white : .red) // Text color changes based on `canGetPaid` Button("Join Group") { isJoiningGroup = true print("Join Group tapped") } - - - } label: { Image(systemName: "plus.circle.fill") .resizable() @@ -101,8 +94,6 @@ struct HomeView: View { } } - - .navigationDestination(isPresented: $isCreatingGroup) { CreateGroupView() } @@ -135,12 +126,10 @@ private func listenToTransactionsForGroup(groupId: String) { print("Error fetching documents: \(error!)") return } - if let error = error { print("Error retreiving collection: \(error)") } - - // Find Changes where document is a diff + // Find Changes where document is a diff snapshots.documentChanges.forEach { diff in if diff.type == .modified { // Check if the proper field is adjusted @@ -176,7 +165,6 @@ private func listenToTransactionsForGroup(groupId: String) { struct BottomToolbar: View { @EnvironmentObject var paymentManager: PaymentManager // Ensure this is passed down from the parent view - var body: some View { HStack(spacing: 0.2) { ToolbarItem(iconName: "person.2", text: "Friends", destination: AnyView(FriendsView())) From 9277f0e009d089e598776e6a0f43756fb6d3c9b6 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Thu, 21 Mar 2024 22:50:15 -0700 Subject: [PATCH 03/20] Updated Code --- reciept bill splitter.xcodeproj/project.pbxproj | 2 -- reciept bill splitter/HomeView.swift | 4 +++- reciept bill splitter/ViewModels/UserViewModel.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reciept bill splitter.xcodeproj/project.pbxproj b/reciept bill splitter.xcodeproj/project.pbxproj index d173bd0..f857d39 100644 --- a/reciept bill splitter.xcodeproj/project.pbxproj +++ b/reciept bill splitter.xcodeproj/project.pbxproj @@ -77,7 +77,6 @@ 36E9BE752B86CAEA0020BE12 /* AddFriendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFriendView.swift; sourceTree = ""; }; 36E9BE852BA007F10020BE12 /* TransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionView.swift; sourceTree = ""; }; E50B498E2BAA0A640080CEAB /* PaymentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentManager.swift; sourceTree = ""; }; - E52146C82B9FE060007571A2 /* reciept-bill-splitter-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "reciept-bill-splitter-Info.plist"; sourceTree = SOURCE_ROOT; }; E52146C92B9FE12C007571A2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E5AB94682BAAA47F00836C2E /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = ""; }; E5AB946A2BAAA75B00836C2E /* ManualTransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualTransactionView.swift; sourceTree = ""; }; @@ -155,7 +154,6 @@ E5AB946A2BAAA75B00836C2E /* ManualTransactionView.swift */, 32B1A4C42B828F9D00A20FDD /* Preview Content */, 32B1A4BE2B828F9400A20FDD /* reciept_bill_splitterApp.swift */, - E52146C82B9FE060007571A2 /* reciept-bill-splitter-Info.plist */, 32847F292B8D96C60061449F /* ScanReciept.swift */, E52146C92B9FE12C007571A2 /* SceneDelegate.swift */, 36E9BE6D2B86AD6E0020BE12 /* SettingsView.swift */, diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index 867728f..9c97ea8 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -117,7 +117,9 @@ struct HomeView: View { let isTransactionCompleted = data["isCompleted"] as? Bool ?? false if isTransactionCompleted { - + Task { + await DatabaseAPI.assignAllGroupMembersPayment(transaction_id: diff.document.documentID) + } } // Assign Each Member Their Parts to Pay } diff --git a/reciept bill splitter/ViewModels/UserViewModel.swift b/reciept bill splitter/ViewModels/UserViewModel.swift index e4dd006..b411bdb 100644 --- a/reciept bill splitter/ViewModels/UserViewModel.swift +++ b/reciept bill splitter/ViewModels/UserViewModel.swift @@ -109,7 +109,7 @@ class UserViewModel : ObservableObject { "friends": [], // reference document id of other users uid "groups": [], // group collection document ids - "assignedTransaction": [] + "assignedTransaction": [:] ]) print("Document created") From 97da8aa516c7dfcac6c950ebaa62e409e0802077 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Thu, 21 Mar 2024 22:56:26 -0700 Subject: [PATCH 04/20] Re added listener --- reciept bill splitter/HomeView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index 9c97ea8..2c86b2c 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -30,6 +30,11 @@ struct HomeView: View { Text(group.group_name) Text("Invite Code: \(group.invite_code)") } + .onTapGesture { + Task { + listenToTransactionsForGroup(groupId: group.groupID) + } + } } } } From b3ca649af5ef6f236efbb43d97d852ea010ee10d Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Thu, 21 Mar 2024 22:57:25 -0700 Subject: [PATCH 05/20] changed function --- reciept bill splitter/HomeView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index 2c86b2c..8dd9b57 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -30,7 +30,7 @@ struct HomeView: View { Text(group.group_name) Text("Invite Code: \(group.invite_code)") } - .onTapGesture { + .onAppear { Task { listenToTransactionsForGroup(groupId: group.groupID) } From c97e30d2de87ce110991d1f5ab320050953238f2 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Thu, 21 Mar 2024 23:47:38 -0700 Subject: [PATCH 06/20] DB API for Getting User Assigned Transactions --- reciept bill splitter/HomeView.swift | 1 + .../ViewModels/DatabaseAPI.swift | 56 +++++++++++++++++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index 8dd9b57..dbd85cc 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -122,6 +122,7 @@ struct HomeView: View { let isTransactionCompleted = data["isCompleted"] as? Bool ?? false if isTransactionCompleted { + print("ASSINING PAYMENTRS") Task { await DatabaseAPI.assignAllGroupMembersPayment(transaction_id: diff.document.documentID) } diff --git a/reciept bill splitter/ViewModels/DatabaseAPI.swift b/reciept bill splitter/ViewModels/DatabaseAPI.swift index 2254781..d3ee909 100644 --- a/reciept bill splitter/ViewModels/DatabaseAPI.swift +++ b/reciept bill splitter/ViewModels/DatabaseAPI.swift @@ -59,6 +59,46 @@ class DatabaseAPI { return nil } + static func grabUserAssignedTransactions() async -> [AssignedTransaction]? { + guard let user = Auth.auth().currentUser else { + print("User Does not exist") + return nil + } + + let userRef = db.collection("users").document(user.uid) + + do { + let document = try await userRef.getDocument() + + var userAssignedTransactions: [AssignedTransaction] = [] + + if document.exists { + let data = document.data() + guard let data = data else { + return nil + } + let assignedTransactions = data["assignedTransaction"] as? [String : [String : Any]] ?? [:] + + for (transaction_id, dataDict) in assignedTransactions { + let ammountToPay = dataDict["ammountToPay"] as? Int ?? 0 + let associatedTransaction_id = transaction_id + let isPaid = dataDict["isPaid"] as? Bool ?? false + let transactionName = data["transactionName"] as? String ?? "Unknown Transaction Name" + let user_idToPay = data["user_idToPay"] as? String ?? "" + + let newAssignment = AssignedTransaction(transactionName: transactionName, associatedTransaction_id: associatedTransaction_id, user_idToPay: user_idToPay, isPaid: isPaid, amountToPay: ammountToPay) + + userAssignedTransactions.append(newAssignment) + } + + return userAssignedTransactions + } + } catch { + print("Error Grabbing User Assigned Transactions: \(error)") + } + return nil + } + static func createGroup(groupName: String) async -> Void { guard let user = Auth.auth().currentUser else { print("User Does not exist") @@ -375,17 +415,22 @@ class DatabaseAPI { newItemList.append(newItem) } - // An Abomination of Code - for groupMember in groupMembers { - let userReference = db.collection("users").document(groupMember) + var groupMemberReferences: [DocumentReference] = [] + + for (index, groupMember) in groupMembers.enumerated() { + groupMemberReferences.append(db.collection("users").document(groupMember)) let userDocument: DocumentSnapshot do { - try userDocument = transaction.getDocument(userReference) + try userDocument = transaction.getDocument(groupMemberReferences[index]) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil } + } + + // An Abomination of Code + for (groupMemberIndex, groupMember) in groupMembers.enumerated() { var totalCostToPay: Float = 0 // Check every item for user @@ -410,7 +455,7 @@ class DatabaseAPI { assignedTransactionDict["isPaid"] = false assignedTransactionDict["ammountToPay"] = totalCostToPay - transaction.updateData(["assignedTransaction.\(transaction_id)": assignedTransactionDict], forDocument: userReference) + transaction.updateData(["assignedTransaction.\(transaction_id)": assignedTransactionDict], forDocument: groupMemberReferences[groupMemberIndex]) } print("FINISHED") return nil @@ -430,6 +475,7 @@ class DatabaseAPI { } + static func retrieveStripeCustomerId(uid: String, completion: @escaping (String?) -> Void) { let db = Firestore.firestore() let customerRef = db.collection("customers").document(uid) From 7f73d257d918d978451a2765aee06e72888d2d65 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Thu, 21 Mar 2024 23:50:10 -0700 Subject: [PATCH 07/20] Added Comment --- reciept bill splitter/ViewModels/DatabaseAPI.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/reciept bill splitter/ViewModels/DatabaseAPI.swift b/reciept bill splitter/ViewModels/DatabaseAPI.swift index d3ee909..7b4dd87 100644 --- a/reciept bill splitter/ViewModels/DatabaseAPI.swift +++ b/reciept bill splitter/ViewModels/DatabaseAPI.swift @@ -79,6 +79,7 @@ class DatabaseAPI { } let assignedTransactions = data["assignedTransaction"] as? [String : [String : Any]] ?? [:] + // Create new AssignedTransaction from list in DB and return it for (transaction_id, dataDict) in assignedTransactions { let ammountToPay = dataDict["ammountToPay"] as? Int ?? 0 let associatedTransaction_id = transaction_id From 6fbe88e509c3fd05f99be59eec77bd77d24ac204 Mon Sep 17 00:00:00 2001 From: diegomtz5 Date: Thu, 21 Mar 2024 23:52:36 -0700 Subject: [PATCH 08/20] merge --- reciept bill splitter/HomeView.swift | 5 +-- reciept bill splitter/TransactionView.swift | 31 ++++++++++++++++ .../ViewModels/DatabaseAPI.swift | 2 +- .../ViewModels/PaymentManager.swift | 35 ++++++++++++++++++- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index 6c92ccb..b0ed05b 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -93,7 +93,9 @@ struct HomeView: View { .navigationDestination(isPresented: $isCreatingGroup) { CreateGroupView() } - + .navigationDestination(isPresented: $isJoiningGroup) { + JoinGroupView() + } BottomToolbar().environmentObject(paymentManager) } .navigationTitle("Home") @@ -129,7 +131,6 @@ private func listenToTransactionsForGroup(groupId: String) { if let error = error { print("Error retreiving collection: (error)") } - // Find Changes where document is a diff snapshots.documentChanges.forEach { diff in if diff.type == .modified { diff --git a/reciept bill splitter/TransactionView.swift b/reciept bill splitter/TransactionView.swift index a1dadf8..940b2c7 100644 --- a/reciept bill splitter/TransactionView.swift +++ b/reciept bill splitter/TransactionView.swift @@ -1,4 +1,7 @@ import SwiftUI +import Firebase +import SwiftUI +import StripePaymentSheet struct TransactionView: View { @@ -66,6 +69,22 @@ struct TransactionView: View { transactionData = await DatabaseAPI.grabTransaction(transaction_id: selectedTransactionId) } } + .onAppear { + let transactionRef = Firestore.firestore().collection("transactions").document(selectedTransactionId) + transactionRef.addSnapshotListener { documentSnapshot, error in + guard let document = documentSnapshot, error == nil else { + print("Error fetching document: \(error?.localizedDescription ?? "Unknown error")") + return + } + guard let data = document.data() else { + print("Document data was empty.") + return + } + // Parse the data into your Transaction model and update the state + self.transactionData = self.parseTransactionData(data) + } + } + .navigationTitle("Transaction Details") @@ -83,5 +102,17 @@ struct TransactionView: View { return totalContribution } + func parseTransactionData(_ data: [String: Any]) -> Transaction { + // Parse the data into your Transaction model + // Example: + let name = data["name"] as? String ?? "Unknown" + let itemList = data["itemList"] as? [[String: Any]] ?? [] + let itemObjects = itemList.map { itemData -> Item in + let name = itemData["name"] as? String ?? "Item" + let priceInCents = itemData["priceInCents"] as? Int ?? 0 + return Item(priceInCents: priceInCents, name: name) + } + return Transaction(name: name, itemList: itemObjects, ...) + } } diff --git a/reciept bill splitter/ViewModels/DatabaseAPI.swift b/reciept bill splitter/ViewModels/DatabaseAPI.swift index 2254781..26b3592 100644 --- a/reciept bill splitter/ViewModels/DatabaseAPI.swift +++ b/reciept bill splitter/ViewModels/DatabaseAPI.swift @@ -598,5 +598,5 @@ class DatabaseAPI { } } - } + } diff --git a/reciept bill splitter/ViewModels/PaymentManager.swift b/reciept bill splitter/ViewModels/PaymentManager.swift index 089b209..c370ca3 100644 --- a/reciept bill splitter/ViewModels/PaymentManager.swift +++ b/reciept bill splitter/ViewModels/PaymentManager.swift @@ -2,6 +2,13 @@ import StripePaymentSheet import FirebaseFunctions import SwiftUI +import Foundation +import SwiftUI +import FirebaseAuth +import FirebaseFirestore +import FirebaseFirestoreSwift +import Firebase + class PaymentManager: ObservableObject { @Published var paymentResult: PaymentSheetResult? @Published var paymentSheet: PaymentSheet? @@ -115,7 +122,7 @@ class PaymentManager: ObservableObject { func createStripeAccountLink(stripeAccountID: String) { - + let functions = Functions.functions() functions.httpsCallable("createAccountLink").call(["accountId": stripeAccountID]) { result, error in if let error = error as NSError? { @@ -151,4 +158,30 @@ class PaymentManager: ObservableObject { } } } + func getStripeConnectAccountIdByEmail(email: String, completion: @escaping (String?, Error?) -> Void) { + let customersRef = Firestore.firestore().collection("customers") + customersRef.whereField("email", isEqualTo: email).getDocuments { (querySnapshot, error) in + if let error = error { + print("Error getting documents: \(error)") + completion(nil, error) + return + } + + guard let document = querySnapshot?.documents.first else { + print("No documents found") + completion(nil, NSError(domain: "FirestoreError", code: 404, userInfo: [NSLocalizedDescriptionKey: "Document not found"])) + return + } + + let data = document.data() + if let stripeConnectAccountId = data["stripeConnectAccountId"] as? String { + print("Found Stripe Connect Account ID: \(stripeConnectAccountId)") + completion(stripeConnectAccountId, nil) + } else { + print("Stripe Connect Account ID not found in document") + completion(nil, NSError(domain: "FirestoreError", code: 404, userInfo: [NSLocalizedDescriptionKey: "Stripe Connect Account ID not found"])) + } + } + } + } From df2a9be2f018c0e821504edba9f7de8bcc5a1f95 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Fri, 22 Mar 2024 07:44:38 -0700 Subject: [PATCH 09/20] Binding to State variable for logging out and dismiss --- .../project.pbxproj | 12 +- reciept bill splitter/.DS_Store | Bin 8196 -> 10244 bytes reciept bill splitter/AccountView.swift | 193 +----------------- .../AllAssignedTransactions.swift | 40 ++++ .../AssignedTransactionDetails.swift | 18 ++ reciept bill splitter/HistoryView.swift | 37 ---- reciept bill splitter/HomeView.swift | 11 +- reciept bill splitter/LaunchScreenView.swift | 2 +- 8 files changed, 81 insertions(+), 232 deletions(-) create mode 100644 reciept bill splitter/AllAssignedTransactions.swift create mode 100644 reciept bill splitter/AssignedTransactionDetails.swift delete mode 100644 reciept bill splitter/HistoryView.swift diff --git a/reciept bill splitter.xcodeproj/project.pbxproj b/reciept bill splitter.xcodeproj/project.pbxproj index f857d39..246e657 100644 --- a/reciept bill splitter.xcodeproj/project.pbxproj +++ b/reciept bill splitter.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 32847F2A2B8D96C60061449F /* ScanReciept.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32847F292B8D96C60061449F /* ScanReciept.swift */; }; 32847F2C2B8D96D50061449F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32847F2B2B8D96D50061449F /* ContentView.swift */; }; 328EEB5C2B9199530021F461 /* DatabaseAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 328EEB5B2B9199520021F461 /* DatabaseAPI.swift */; }; + 329A95DE2BADC39F0028FDEF /* AssignedTransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 329A95DD2BADC39F0028FDEF /* AssignedTransactionDetails.swift */; }; 32A6DA972BA13C81005FAA2C /* JoinGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6DA962BA13C81005FAA2C /* JoinGroupView.swift */; }; 32B1A4BF2B828F9400A20FDD /* reciept_bill_splitterApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B1A4BE2B828F9400A20FDD /* reciept_bill_splitterApp.swift */; }; 32B1A4C32B828F9D00A20FDD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 32B1A4C22B828F9D00A20FDD /* Assets.xcassets */; }; @@ -27,7 +28,7 @@ 36E9BE642B86ACE00020BE12 /* SignUpLogInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE632B86ACE00020BE12 /* SignUpLogInView.swift */; }; 36E9BE662B86AD1C0020BE12 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE652B86AD1C0020BE12 /* HomeView.swift */; }; 36E9BE682B86AD3F0020BE12 /* FriendsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE672B86AD3F0020BE12 /* FriendsView.swift */; }; - 36E9BE6C2B86AD5D0020BE12 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE6B2B86AD5D0020BE12 /* HistoryView.swift */; }; + 36E9BE6C2B86AD5D0020BE12 /* AllAssignedTransactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE6B2B86AD5D0020BE12 /* AllAssignedTransactions.swift */; }; 36E9BE6E2B86AD6E0020BE12 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE6D2B86AD6E0020BE12 /* SettingsView.swift */; }; 36E9BE702B86AD8D0020BE12 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE6F2B86AD8D0020BE12 /* SplitView.swift */; }; 36E9BE722B86B6240020BE12 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE712B86B6240020BE12 /* SignUpView.swift */; }; @@ -55,6 +56,7 @@ 32847F292B8D96C60061449F /* ScanReciept.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScanReciept.swift; sourceTree = ""; }; 32847F2B2B8D96D50061449F /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 328EEB5B2B9199520021F461 /* DatabaseAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseAPI.swift; sourceTree = ""; }; + 329A95DD2BADC39F0028FDEF /* AssignedTransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssignedTransactionDetails.swift; sourceTree = ""; }; 32A6DA962BA13C81005FAA2C /* JoinGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupView.swift; sourceTree = ""; }; 32B1A4BB2B828F9400A20FDD /* reciept bill splitter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "reciept bill splitter.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 32B1A4BE2B828F9400A20FDD /* reciept_bill_splitterApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = reciept_bill_splitterApp.swift; sourceTree = ""; }; @@ -69,7 +71,7 @@ 36E9BE632B86ACE00020BE12 /* SignUpLogInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpLogInView.swift; sourceTree = ""; }; 36E9BE652B86AD1C0020BE12 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 36E9BE672B86AD3F0020BE12 /* FriendsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsView.swift; sourceTree = ""; }; - 36E9BE6B2B86AD5D0020BE12 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = ""; }; + 36E9BE6B2B86AD5D0020BE12 /* AllAssignedTransactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllAssignedTransactions.swift; sourceTree = ""; }; 36E9BE6D2B86AD6E0020BE12 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 36E9BE6F2B86AD8D0020BE12 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; 36E9BE712B86B6240020BE12 /* SignUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = ""; }; @@ -147,7 +149,7 @@ 36E9BE672B86AD3F0020BE12 /* FriendsView.swift */, 32E5E1882B8DA66C005BF4F4 /* GoogleService-Info.plist */, 36788E3E2BA50F22006925A6 /* GroupDetailView.swift */, - 36E9BE6B2B86AD5D0020BE12 /* HistoryView.swift */, + 36E9BE6B2B86AD5D0020BE12 /* AllAssignedTransactions.swift */, 36E9BE652B86AD1C0020BE12 /* HomeView.swift */, 32A6DA962BA13C81005FAA2C /* JoinGroupView.swift */, 32B1A4C02B828F9400A20FDD /* LaunchScreenView.swift */, @@ -164,6 +166,7 @@ 32847F272B8D96B70061449F /* UIImageExtension.swift */, 32C3CD472B8711A700FB2BF1 /* ViewModels */, E5E6F0D72BAD36A0004A0697 /* CreateGroupView.swift */, + 329A95DD2BADC39F0028FDEF /* AssignedTransactionDetails.swift */, ); path = "reciept bill splitter"; sourceTree = ""; @@ -290,7 +293,7 @@ 328EEB5C2B9199530021F461 /* DatabaseAPI.swift in Sources */, E5E6F0D82BAD36A0004A0697 /* CreateGroupView.swift in Sources */, 32847F282B8D96B70061449F /* UIImageExtension.swift in Sources */, - 36E9BE6C2B86AD5D0020BE12 /* HistoryView.swift in Sources */, + 36E9BE6C2B86AD5D0020BE12 /* AllAssignedTransactions.swift in Sources */, 36E9BE6E2B86AD6E0020BE12 /* SettingsView.swift in Sources */, 36E9BE682B86AD3F0020BE12 /* FriendsView.swift in Sources */, 36788E3F2BA50F22006925A6 /* GroupDetailView.swift in Sources */, @@ -300,6 +303,7 @@ 36E9BE762B86CAEA0020BE12 /* AddFriendView.swift in Sources */, E5AB946B2BAAA75B00836C2E /* ManualTransactionView.swift in Sources */, E52146CA2B9FE12C007571A2 /* SceneDelegate.swift in Sources */, + 329A95DE2BADC39F0028FDEF /* AssignedTransactionDetails.swift in Sources */, 36E9BE702B86AD8D0020BE12 /* SplitView.swift in Sources */, E50B498F2BAA0A640080CEAB /* PaymentManager.swift in Sources */, 36E9BE722B86B6240020BE12 /* SignUpView.swift in Sources */, diff --git a/reciept bill splitter/.DS_Store b/reciept bill splitter/.DS_Store index 1b58d38837910cadb8c678d9150dc762b99c246a..6bb6028cd94cf31a1bf139a47a51507bab9538a1 100644 GIT binary patch delta 212 zcmZp1XbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~50$SAQfU^hRb#AY6W80O8(goRi{ zxfsG2G8s}C${Bna@)=TqtQ>~o$!A3xI7|%;bQDZ1EGCDF+ACsJRu){8my@5D4zz)B zW8nmLL1rKe2o$)1ge%C}jfLNtC-ch$iZDSOqXCj;WMD7=(US{gdN-V7i0$N2LcIhAmIu!YGdJd=E?jjfgFqw X3m7KH^Guz*LCAMAk7yp##4;ZMLku9k diff --git a/reciept bill splitter/AccountView.swift b/reciept bill splitter/AccountView.swift index a72a5ec..5067b20 100644 --- a/reciept bill splitter/AccountView.swift +++ b/reciept bill splitter/AccountView.swift @@ -1,183 +1,4 @@ -/*import SwiftUI -import FirebaseAuth - -struct AccountView: View { - @State private var isEditing = false - @State private var newUsername = "" - @State private var userEmail = "" // Add state to store user email - @State private var userCanGetPaid = false // Add state to store user email - @State private var user_id = "" - - @EnvironmentObject var user: UserViewModel - @EnvironmentObject var paymentManager: PaymentManager - @State private var balanceData: [String: Any]? = nil - @State var isLoggedOut = false - var body: some View { - NavigationStack{ - VStack { - HStack { - if isEditing { - TextField("Enter new username", text: $newUsername) - .padding() - .textFieldStyle(RoundedBorderTextFieldStyle()) - } else { - Text("Current Username: " + (newUsername.isEmpty ? "N/A" : newUsername)) - .padding() - } - Button(action: { - isEditing.toggle() - }) { - Image(systemName: isEditing ? "checkmark.circle.fill" : "pencil.circle.fill") - .foregroundColor(isEditing ? .green : .blue) - } - } - - if isEditing { - Button("Save") { - Task { - await user.updateUserName(newName: newUsername) - isEditing.toggle() - } - } - .padding() - } - Text("Email: \(userEmail)") // Display user email - .padding() - // Stripe balance section - if user.canGetPaid { - if let balanceData = balanceData { - VStack { - Text("Stripe Balance") - .font(.title) - .padding() - if let availableArray = balanceData["available"] as? [[String: Any]], - let available = availableArray.first, - let availableAmount = available["amount"] as? Int { - Text("Available Balance: \(formatAmount(availableAmount))") - } - if let pendingArray = balanceData["pending"] as? [[String: Any]], - let pending = pendingArray.first, - let pendingAmount = pending["amount"] as? Int { - Text("Pending Balance: \(formatAmount(pendingAmount))") - } - Button("Update payment methods") { - print("creating link") - DatabaseAPI.getStripeConnectAccountId { accountId, error in - if let error = error { - print("Error retrieving account ID: \(error.localizedDescription)") - } else if let accountId = accountId { - print("Retrieved Stripe Connect Account ID: \(accountId)") - // Use the accountId for whatever you need, like creating an account link - paymentManager.createStripeAccountLink(stripeAccountID: accountId) - } else { - print("Stripe Connect Account ID not found") - } - } - } - } - } else { - Text("") - } - } - else { - Button("Setup Payments") { - print("creating account") - paymentManager.createExpressConnectAccountAndOnboardingLink(email: userEmail) - - //SETUP after onboarding it is the only way and have to check for reauth fuckkkkkkkkkkk (setup the cangetpaid of course) - - DatabaseAPI.setCanGetPaid(forUserId: user_id, canGetPaid: true) { error in // Pass the userId here - if let error = error { - // Handle the error - print("Error setting canGetPaid: \(error.localizedDescription)") - } else { - // Update was successful - self.userCanGetPaid = true - user.canGetPaid = true - print("canGetPaid successfully set for the user") - } - } - } - } - } - .navigationTitle("Accounts") - .onAppear { - Task{ - await fetchUserDetails() - await fetchStripeBalance() - await user.updateCanGetPaidStatus() - - } - } - Button(action: { - // Sign out action - do { - try Auth.auth().signOut() - isLoggedOut = true // Set isLoggedIn to false to navigate to SignUpView - - } catch { - print("Error signing out: \(error.localizedDescription)") - } - }) { - Text("Sign Out") - .foregroundColor(.red) - } - .padding() - } - .navigationDestination(isPresented: $isLoggedOut) { - SignUpLogInView(isLoggedIn: $isLoggedOut) - .navigationBarHidden(true) - - } - } - - - - private func fetchUserDetails() async{ - Task { - if let user = await DatabaseAPI.grabUserData() { - self.newUsername = user.userName - self.userEmail = user.email - } - if let user = Auth.auth().currentUser { - self.user_id = user.uid - } - } - } - - private func fetchStripeBalance() async { - // Check if the current user has a Stripe Connect Account ID - DatabaseAPI.getStripeConnectAccountId { accountId, error in - guard let accountId = accountId, error == nil else { - print("Stripe Connect Account ID not found or error occurred: \(error?.localizedDescription ?? "Unknown error")") - // Handle UI update for users without Stripe account here - // For example, show a message or hide the balance section - return - } - - // If the account ID exists, fetch the Stripe balance - paymentManager.checkStripeBalance(accountId: accountId) { result in - switch result { - case .success(let balance): - // Here you can update some state to display the balance in your UI - self.balanceData = balance - print("Retrieved Stripe balance: \(balance)") - case .failure(let error): - print("Error fetching Stripe balance: \(error.localizedDescription)") - } - } - } - } - - private func formatAmount(_ amount: Int) -> String { - let numberFormatter = NumberFormatter() - numberFormatter.numberStyle = .currency - numberFormatter.currencyCode = "USD" - return numberFormatter.string(from: NSNumber(value: Double(amount) / 100)) ?? "$0.00" - } - -}*/ import SwiftUI import FirebaseAuth @@ -191,8 +12,10 @@ struct AccountView: View { @EnvironmentObject var user: UserViewModel @EnvironmentObject var paymentManager: PaymentManager @State private var balanceData: [String: Any]? = nil - @State private var isLoggedOut = false - + @Environment(\.dismiss) var dismiss + + @Binding var isLoggedIn: Bool + var body: some View { NavigationStack { VStack(spacing: 20) { @@ -318,7 +141,9 @@ struct AccountView: View { // Sign out action do { try Auth.auth().signOut() - isLoggedOut = true + isLoggedIn = false + dismiss() + } catch { print("Error signing out: \(error.localizedDescription)") } @@ -333,10 +158,6 @@ struct AccountView: View { .padding() .buttonStyle(PlainButtonStyle()) } - .navigationDestination(isPresented: $isLoggedOut) { - SignUpLogInView(isLoggedIn: $isLoggedOut) - .navigationBarHidden(true) - } } private func fetchUserDetails() async { diff --git a/reciept bill splitter/AllAssignedTransactions.swift b/reciept bill splitter/AllAssignedTransactions.swift new file mode 100644 index 0000000..98d9a67 --- /dev/null +++ b/reciept bill splitter/AllAssignedTransactions.swift @@ -0,0 +1,40 @@ +import SwiftUI +import FirebaseFirestore +import FirebaseFirestoreSwift + +struct AllAssignedTransactions: View { + @EnvironmentObject var user: UserViewModel + @State var allAssignedTransactions: [AssignedTransaction] = [] + + var body: some View { + NavigationStack { + Text("Assigned Transactions") + List { + ForEach(0 ..< allAssignedTransactions.count, id:\.self) { index in + NavigationLink(destination: AssignedTransactionDetails(assignedTransaction: $allAssignedTransactions[index])) { + VStack(alignment: .leading) { + if allAssignedTransactions[index].isPaid { + Text(allAssignedTransactions[index].transactionName) + .opacity(0.5) + } + else { + Text(allAssignedTransactions[index].transactionName) + } + } + } + } + } + } + .onAppear { + fetchAllTransactions() + } + } + + private func fetchAllTransactions() { + Task { + if let userAssignedTransactions = await DatabaseAPI.grabUserAssignedTransactions() { + allAssignedTransactions = userAssignedTransactions + } + } + } +} diff --git a/reciept bill splitter/AssignedTransactionDetails.swift b/reciept bill splitter/AssignedTransactionDetails.swift new file mode 100644 index 0000000..f2005d1 --- /dev/null +++ b/reciept bill splitter/AssignedTransactionDetails.swift @@ -0,0 +1,18 @@ +// +// AssignedTransactionView.swift +// reciept bill splitter +// +// Created by Simon Huang on 3/22/24. +// + +import SwiftUI + +struct AssignedTransactionDetails: View { + + + @Binding var assignedTransaction: AssignedTransaction + + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} diff --git a/reciept bill splitter/HistoryView.swift b/reciept bill splitter/HistoryView.swift deleted file mode 100644 index 8316f54..0000000 --- a/reciept bill splitter/HistoryView.swift +++ /dev/null @@ -1,37 +0,0 @@ -import SwiftUI -import FirebaseFirestore -import FirebaseFirestoreSwift - -struct HistoryView: View { - @EnvironmentObject var user: UserViewModel - @State private var allTransactions: [Transaction] = [] - - var body: some View { - List { - ForEach(allTransactions.indices, id: \.self) { index in - VStack(alignment: .leading) { - Text(allTransactions[index].name) - } - } - } - .onAppear { - fetchAllTransactions() - } - } - - private func fetchAllTransactions() { - Task { - var transactions: [Transaction] = [] - // Fetch transactions for each group - for group in user.groups { - if let groupTransactions = await DatabaseAPI.grabAllTransactionsForGroup(groupID: group.groupID) { - transactions.append(contentsOf: groupTransactions) - } - } - // Sort transactions by time created - DispatchQueue.main.async { - allTransactions = transactions - } - } - } -} diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index dbd85cc..dafc4dd 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -18,6 +18,7 @@ struct HomeView: View { @StateObject private var paymentManager = PaymentManager() @State private var showPaymentSheet = false + @Binding var isLoggedIn: Bool var body: some View { NavigationStack { VStack { @@ -82,7 +83,7 @@ struct HomeView: View { CreateGroupView() } - BottomToolbar().environmentObject(paymentManager) + BottomToolbar(isLoggedIn: $isLoggedIn).environmentObject(paymentManager) } .navigationTitle("Home") .alert(isPresented: $showInfoAlert) { @@ -150,13 +151,15 @@ struct HomeView: View { struct BottomToolbar: View { @EnvironmentObject var paymentManager: PaymentManager // Ensure this is passed down from the parent view - + + @Binding var isLoggedIn: Bool + var body: some View { HStack(spacing: 0.2) { ToolbarItem(iconName: "person.2", text: "Friends", destination: AnyView(FriendsView())) //ToolbarItem(iconName: "person.3", text: "Home", destination: AnyView(HomeView())) - ToolbarItem(iconName: "bolt", text: "Activities", destination: AnyView(HistoryView())) - ToolbarItem(iconName: "person.crop.circle", text: "Accounts", destination: AnyView(AccountView().environmentObject(paymentManager))) + ToolbarItem(iconName: "bolt", text: "Activate Transactions", destination: AnyView(AllAssignedTransactions())) + ToolbarItem(iconName: "person.crop.circle", text: "Accounts", destination: AnyView(AccountView(isLoggedIn: $isLoggedIn).environmentObject(paymentManager))) } .frame(height: 50) .background(Color(UIColor.systemBackground)) diff --git a/reciept bill splitter/LaunchScreenView.swift b/reciept bill splitter/LaunchScreenView.swift index 53a4c5f..d4785dd 100644 --- a/reciept bill splitter/LaunchScreenView.swift +++ b/reciept bill splitter/LaunchScreenView.swift @@ -25,7 +25,7 @@ struct LaunchScreenView: View { if isActive { NavigationStack { if isLoggedIn || router.currentPage == "onboarding" || router.currentPage == "reauth" { - HomeView() + HomeView(isLoggedIn: $isLoggedIn) } else { SignUpLogInView(isLoggedIn: $isLoggedIn) } From f92a8e93d8d08a77f58b8b6d0558ecaaed352c44 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Fri, 22 Mar 2024 08:00:57 -0700 Subject: [PATCH 10/20] Refactored Transaction View and How GroupDetailView handles selecting transactions --- reciept bill splitter/GroupDetailView.swift | 60 +++++++++++++-------- reciept bill splitter/TransactionView.swift | 30 +++++------ 2 files changed, 49 insertions(+), 41 deletions(-) diff --git a/reciept bill splitter/GroupDetailView.swift b/reciept bill splitter/GroupDetailView.swift index a467500..c23bf33 100644 --- a/reciept bill splitter/GroupDetailView.swift +++ b/reciept bill splitter/GroupDetailView.swift @@ -44,30 +44,46 @@ struct GroupDetailView: View { List { ForEach(user.currentSelectedGroupTransactions.indices, id: \.self) { index in + let transactionData = user.currentSelectedGroupTransactions[index] let date = formatter.string(from: transactionData.dateCreated?.dateValue() ?? Date()) - - if transactionData.isCompleted { - HStack { - Text(transactionData.name) - Text(date) - } - .onTapGesture { - user.selectedTransaction = transactionData - selectedTransactionID = transactionData.transaction_id - isTransactionSelected = true - } - .opacity(0.5) - } else { - HStack { - Text(transactionData.name) - Text(date) - } - .onTapGesture { - user.selectedTransaction = transactionData - selectedTransactionID = transactionData.transaction_id - isTransactionSelected = true + NavigationLink(destination: TransactionView(selectedTransactionId: $user.currentSelectedGroupTransactions[index].transaction_id, groupData: $selectedGroup)) { + if transactionData.isCompleted { + HStack { + Text(transactionData.name) + Text(date) + } + .opacity(0.5) + } else { + HStack { + Text(transactionData.name) + Text(date) + } } + + +// if transactionData.isCompleted { +// HStack { +// Text(transactionData.name) +// Text(date) +// } +// .onTapGesture { +// user.selectedTransaction = transactionData +// selectedTransactionID = transactionData.transaction_id +// isTransactionSelected = true +// } +// .opacity(0.5) +// } else { +// HStack { +// Text(transactionData.name) +// Text(date) +// } +// .onTapGesture { +// user.selectedTransaction = transactionData +// selectedTransactionID = transactionData.transaction_id +// isTransactionSelected = true +// } +// } } } } @@ -111,8 +127,6 @@ struct GroupDetailView: View { } .navigationDestination(isPresented: $isTransactionSelected) { TransactionView(selectedTransactionId: $selectedTransactionID, groupData: $selectedGroup) - //TransactionView() - } .navigationDestination(isPresented: $isManualInputPresented) { ManualTransactionInputView(isPresented: $isManualInputPresented, transactionName: $transactionName, transactionPrice: $transactionPrice, groupID: selectedGroup.groupID) diff --git a/reciept bill splitter/TransactionView.swift b/reciept bill splitter/TransactionView.swift index a1dadf8..122f16f 100644 --- a/reciept bill splitter/TransactionView.swift +++ b/reciept bill splitter/TransactionView.swift @@ -9,18 +9,10 @@ struct TransactionView: View { @State var transactionData: Transaction? - var totalSpent: Double { - - if let transaction = user.selectedTransaction { - return transaction.itemList.map { Double($0.priceInCents) / 100 }.reduce(0, +) - } else { - return Double(0.0) - } - } - var body: some View { VStack { if let transaction = transactionData { + let totalSpent = transaction.itemList.map { Double($0.priceInCents) / 100 }.reduce(0, +) Text(transaction.name) .font(.title) @@ -42,24 +34,26 @@ struct TransactionView: View { } } } - Text("Your Total Contribution: $\(String(format: "%.2f", calculateUserTotalContribution(transaction: user.selectedTransaction!, userID: user.user_id)))") + Text("Your Total Contribution: $\(String(format: "%.2f", calculateUserTotalContribution(transaction: transaction, userID: user.user_id)))") .fontWeight(.bold) Text("Total: $\(String(format: "%.2f", totalSpent))") .fontWeight(.bold) + // ONLY group owner can lock in assigned prices + if groupData.owner_id == user.user_id { + Button("Complete Transaction") { + Task { + await DatabaseAPI.toggleGroupTransactionsCompletion(transactionID: transaction.transaction_id, completion: true) + } + } + } + } else { Text("LOADING") } - // ONLY group owner can lock in assigned prices - if groupData.owner_id == user.user_id { - Button("Complete Transaction") { - Task { - await DatabaseAPI.toggleGroupTransactionsCompletion(transactionID: user.selectedTransaction?.transaction_id ?? "", completion: true) - } - } - } + } .onAppear { Task { From 3bbbfb35e413c91e3fa7215f1af2b7ab1c31ab29 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Fri, 22 Mar 2024 08:06:56 -0700 Subject: [PATCH 11/20] Removed Unncessary code --- .../project.pbxproj | 4 -- reciept bill splitter/GroupDetailView.swift | 37 ------------------- 2 files changed, 41 deletions(-) diff --git a/reciept bill splitter.xcodeproj/project.pbxproj b/reciept bill splitter.xcodeproj/project.pbxproj index 246e657..0139312 100644 --- a/reciept bill splitter.xcodeproj/project.pbxproj +++ b/reciept bill splitter.xcodeproj/project.pbxproj @@ -47,7 +47,6 @@ E591A68D2B9E8AD600AC1F5F /* StripePaymentSheet in Frameworks */ = {isa = PBXBuildFile; productRef = E591A68C2B9E8AD600AC1F5F /* StripePaymentSheet */; }; E591A68F2B9E8AD600AC1F5F /* StripePayments in Frameworks */ = {isa = PBXBuildFile; productRef = E591A68E2B9E8AD600AC1F5F /* StripePayments */; }; E591A6912B9E8AD600AC1F5F /* StripePaymentsUI in Frameworks */ = {isa = PBXBuildFile; productRef = E591A6902B9E8AD600AC1F5F /* StripePaymentsUI */; }; - E5AB946B2BAAA75B00836C2E /* ManualTransactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AB946A2BAAA75B00836C2E /* ManualTransactionView.swift */; }; E5E6F0D82BAD36A0004A0697 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E6F0D72BAD36A0004A0697 /* CreateGroupView.swift */; }; /* End PBXBuildFile section */ @@ -81,7 +80,6 @@ E50B498E2BAA0A640080CEAB /* PaymentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentManager.swift; sourceTree = ""; }; E52146C92B9FE12C007571A2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; E5AB94682BAAA47F00836C2E /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = ""; }; - E5AB946A2BAAA75B00836C2E /* ManualTransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualTransactionView.swift; sourceTree = ""; }; E5E6F0D72BAD36A0004A0697 /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -153,7 +151,6 @@ 36E9BE652B86AD1C0020BE12 /* HomeView.swift */, 32A6DA962BA13C81005FAA2C /* JoinGroupView.swift */, 32B1A4C02B828F9400A20FDD /* LaunchScreenView.swift */, - E5AB946A2BAAA75B00836C2E /* ManualTransactionView.swift */, 32B1A4C42B828F9D00A20FDD /* Preview Content */, 32B1A4BE2B828F9400A20FDD /* reciept_bill_splitterApp.swift */, 32847F292B8D96C60061449F /* ScanReciept.swift */, @@ -301,7 +298,6 @@ 32847F2C2B8D96D50061449F /* ContentView.swift in Sources */, 36E9BE742B86C18D0020BE12 /* AccountView.swift in Sources */, 36E9BE762B86CAEA0020BE12 /* AddFriendView.swift in Sources */, - E5AB946B2BAAA75B00836C2E /* ManualTransactionView.swift in Sources */, E52146CA2B9FE12C007571A2 /* SceneDelegate.swift in Sources */, 329A95DE2BADC39F0028FDEF /* AssignedTransactionDetails.swift in Sources */, 36E9BE702B86AD8D0020BE12 /* SplitView.swift in Sources */, diff --git a/reciept bill splitter/GroupDetailView.swift b/reciept bill splitter/GroupDetailView.swift index c23bf33..50514f0 100644 --- a/reciept bill splitter/GroupDetailView.swift +++ b/reciept bill splitter/GroupDetailView.swift @@ -12,7 +12,6 @@ import FirebaseFirestoreSwift struct GroupDetailView: View { let db = Firestore.firestore() @State private var isCameraPresented = false - @State private var isTransactionSelected = false @State private var selectedImage: UIImage? @State private var isTaken = false @@ -20,10 +19,7 @@ struct GroupDetailView: View { @State private var totalSpent: Double = 0 @State private var isViewMembersPopoverPresented = false - - @State private var isManualInputPresented = false - @State private var transactionName = "" @State private var transactionPrice = "" @@ -60,36 +56,9 @@ struct GroupDetailView: View { Text(date) } } - - -// if transactionData.isCompleted { -// HStack { -// Text(transactionData.name) -// Text(date) -// } -// .onTapGesture { -// user.selectedTransaction = transactionData -// selectedTransactionID = transactionData.transaction_id -// isTransactionSelected = true -// } -// .opacity(0.5) -// } else { -// HStack { -// Text(transactionData.name) -// Text(date) -// } -// .onTapGesture { -// user.selectedTransaction = transactionData -// selectedTransactionID = transactionData.transaction_id -// isTransactionSelected = true -// } -// } } } } - Button("Add Transaction") { - isManualInputPresented.toggle() - } } else { Text("No transactions found") } @@ -125,12 +94,6 @@ struct GroupDetailView: View { MembersListView(members: selectedGroup.members) } } - .navigationDestination(isPresented: $isTransactionSelected) { - TransactionView(selectedTransactionId: $selectedTransactionID, groupData: $selectedGroup) - } - .navigationDestination(isPresented: $isManualInputPresented) { - ManualTransactionInputView(isPresented: $isManualInputPresented, transactionName: $transactionName, transactionPrice: $transactionPrice, groupID: selectedGroup.groupID) - } .onChange(of: scanReceipt.isScanning) { if !scanReceipt.isScanning { Task { From 4e0d5d3a38b4b8bd987a3c8e23f0f09bd728a975 Mon Sep 17 00:00:00 2001 From: diegomtz5 Date: Fri, 22 Mar 2024 08:28:40 -0700 Subject: [PATCH 12/20] started with deleting items --- reciept bill splitter/TransactionView.swift | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/reciept bill splitter/TransactionView.swift b/reciept bill splitter/TransactionView.swift index 940b2c7..c541a35 100644 --- a/reciept bill splitter/TransactionView.swift +++ b/reciept bill splitter/TransactionView.swift @@ -81,7 +81,7 @@ struct TransactionView: View { return } // Parse the data into your Transaction model and update the state - self.transactionData = self.parseTransactionData(data) + // self.transactionData = self.parseTransactionData(data) } } @@ -102,17 +102,17 @@ struct TransactionView: View { return totalContribution } - func parseTransactionData(_ data: [String: Any]) -> Transaction { - // Parse the data into your Transaction model - // Example: - let name = data["name"] as? String ?? "Unknown" - let itemList = data["itemList"] as? [[String: Any]] ?? [] - let itemObjects = itemList.map { itemData -> Item in - let name = itemData["name"] as? String ?? "Item" - let priceInCents = itemData["priceInCents"] as? Int ?? 0 - return Item(priceInCents: priceInCents, name: name) - } - return Transaction(name: name, itemList: itemObjects, ...) - } +// func parseTransactionData(_ data: [String: Any]) -> Transaction { +// // Parse the data into your Transaction model +// // Example: +// let name = data["name"] as? String ?? "Unknown" +// let itemList = data["itemList"] as? [[String: Any]] ?? [] +// let itemObjects = itemList.map { itemData -> Item in +// let name = itemData["name"] as? String ?? "Item" +// let priceInCents = itemData["priceInCents"] as? Int ?? 0 +// return Item(priceInCents: priceInCents, name: name) +// } +// return Transaction(name: name, itemList: itemObjects, ...) +// } } From 04c69ed7542f823890920711e86508288f8f34b0 Mon Sep 17 00:00:00 2001 From: diegomtz5 Date: Fri, 22 Mar 2024 11:33:38 -0700 Subject: [PATCH 13/20] Payments in transactions --- reciept bill splitter/AccountView.swift | 36 +++++----- .../AllAssignedTransactions.swift | 5 +- .../AssignedTransactionDetails.swift | 69 ++++++++++++++++--- reciept bill splitter/GroupDetailView.swift | 2 +- reciept bill splitter/HomeView.swift | 34 ++------- reciept bill splitter/LaunchScreenView.swift | 30 ++++++-- reciept bill splitter/SignUpLogInView.swift | 36 ++++++++-- .../ViewModels/DatabaseAPI.swift | 52 ++++++++++---- .../ViewModels/PaymentManager.swift | 12 +++- 9 files changed, 191 insertions(+), 85 deletions(-) diff --git a/reciept bill splitter/AccountView.swift b/reciept bill splitter/AccountView.swift index 5067b20..520f672 100644 --- a/reciept bill splitter/AccountView.swift +++ b/reciept bill splitter/AccountView.swift @@ -1,4 +1,3 @@ - import SwiftUI import FirebaseAuth @@ -8,16 +7,17 @@ struct AccountView: View { @State private var userEmail = "" @State private var userCanGetPaid = false @State private var user_id = "" - + @EnvironmentObject var user: UserViewModel @EnvironmentObject var paymentManager: PaymentManager @State private var balanceData: [String: Any]? = nil + @Environment(\.dismiss) var dismiss @Binding var isLoggedIn: Bool var body: some View { - NavigationStack { + NavigationStack{ VStack(spacing: 20) { VStack(alignment: .leading, spacing: 10) { HStack { @@ -48,7 +48,7 @@ struct AccountView: View { .foregroundColor(.white) .padding() .frame(maxWidth: .infinity) - .background(Color.blue) + .background(LinearGradient(gradient: Gradient(colors: [Color.blue.opacity(0.8), Color.purple.opacity(0.8)]), startPoint: .leading, endPoint: .trailing)) .cornerRadius(8) } } @@ -100,7 +100,7 @@ struct AccountView: View { } .foregroundColor(.white) .padding() - .background(Color.blue) + .background(LinearGradient(gradient: Gradient(colors: [Color.blue.opacity(0.8), Color.purple.opacity(0.8)]), startPoint: .leading, endPoint: .trailing)) .cornerRadius(8) } .padding() @@ -111,15 +111,17 @@ struct AccountView: View { print("Creating account") paymentManager.createExpressConnectAccountAndOnboardingLink(email: userEmail) - DatabaseAPI.setCanGetPaid(forUserId: user_id, canGetPaid: true) { error in - if let error = error { - print("Error setting canGetPaid: \(error.localizedDescription)") - } else { - self.userCanGetPaid = true - user.canGetPaid = true - print("canGetPaid successfully set for the user") - } - } + DatabaseAPI.setCanGetPaid(forUserId: user_id, canGetPaid: true) { error in // Pass the userId here + if let error = error { + // Handle the error + print("Error setting canGetPaid: \(error.localizedDescription)") + } else { + // Update was successful + self.userCanGetPaid = true + user.canGetPaid = true + print("canGetPaid successfully set for the user") + } + } } .foregroundColor(.white) .padding() @@ -138,12 +140,11 @@ struct AccountView: View { } Button(action: { - // Sign out action + isLoggedIn = false do { try Auth.auth().signOut() isLoggedIn = false dismiss() - } catch { print("Error signing out: \(error.localizedDescription)") } @@ -152,8 +153,7 @@ struct AccountView: View { .foregroundColor(.black) .padding() .frame(maxWidth: .infinity) - .background(Color.gray.opacity(0.2)) - .cornerRadius(8) + .background(LinearGradient(gradient: Gradient(colors: [Color.blue.opacity(0.8), Color.purple.opacity(0.8)]), startPoint: .leading, endPoint: .trailing)) .cornerRadius(8) } .padding() .buttonStyle(PlainButtonStyle()) diff --git a/reciept bill splitter/AllAssignedTransactions.swift b/reciept bill splitter/AllAssignedTransactions.swift index 98d9a67..7caa535 100644 --- a/reciept bill splitter/AllAssignedTransactions.swift +++ b/reciept bill splitter/AllAssignedTransactions.swift @@ -5,13 +5,14 @@ import FirebaseFirestoreSwift struct AllAssignedTransactions: View { @EnvironmentObject var user: UserViewModel @State var allAssignedTransactions: [AssignedTransaction] = [] - + @EnvironmentObject var paymentManager: PaymentManager + var body: some View { NavigationStack { Text("Assigned Transactions") List { ForEach(0 ..< allAssignedTransactions.count, id:\.self) { index in - NavigationLink(destination: AssignedTransactionDetails(assignedTransaction: $allAssignedTransactions[index])) { + NavigationLink(destination: AssignedTransactionDetails(assignedTransaction: $allAssignedTransactions[index]).environmentObject(user).environmentObject(paymentManager)) { VStack(alignment: .leading) { if allAssignedTransactions[index].isPaid { Text(allAssignedTransactions[index].transactionName) diff --git a/reciept bill splitter/AssignedTransactionDetails.swift b/reciept bill splitter/AssignedTransactionDetails.swift index f2005d1..2fa54b7 100644 --- a/reciept bill splitter/AssignedTransactionDetails.swift +++ b/reciept bill splitter/AssignedTransactionDetails.swift @@ -1,18 +1,67 @@ -// -// AssignedTransactionView.swift -// reciept bill splitter -// -// Created by Simon Huang on 3/22/24. -// - import SwiftUI +import StripePaymentSheet struct AssignedTransactionDetails: View { - - + @EnvironmentObject var paymentManager: PaymentManager + @EnvironmentObject var user: UserViewModel @Binding var assignedTransaction: AssignedTransaction + @Environment(\.dismiss) var dismiss // Use the dismiss environment value + var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + VStack { + Text("Transaction Name: \(assignedTransaction.transactionName)") + .font(.headline) + Text("Transaction Name: \(assignedTransaction.associatedTransaction_id)") + .font(.headline) + Text("Amount to Pay: $\(String(format: "%.2f", Double(assignedTransaction.amountToPay) / 100))") + .font(.subheadline) + + if !assignedTransaction.isPaid { + if let paymentSheet = paymentManager.paymentSheet { + PaymentSheet.PaymentButton(paymentSheet: paymentSheet) { paymentResult in + // This is the completion handler + paymentManager.onPaymentCompletion(result: paymentResult) + if case .completed = paymentResult { + // Call transferMoney function after successful payment + paymentManager.transferMoney(amount: assignedTransaction.amountToPay, destinationAccountId: assignedTransaction.user_idToPay, assignedTransactionId: assignedTransaction.associatedTransaction_id) + + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // Optional delay to allow users to see the completion message + dismiss() // Dismiss the view + } + } + } content: { + Text("Pay Now") + .padding() + .background(Color.blue) + .foregroundColor(Color.white) + .cornerRadius(10) + } + } else { + Text("Loading…") + } + if let result = paymentManager.paymentResult { + switch result { + case .completed: + Text("Payment complete") + case .failed(let error): + Text("Payment failed: \(error.localizedDescription)") + case .canceled: + Text("Payment canceled.") + } + } + + } else { + Text("Transaction already paid") + .foregroundColor(.green) + } + } + .onAppear(){ + print(assignedTransaction) + paymentManager.fetchPaymentDataAndPrepareSheet(uid: user.user_id, amount: assignedTransaction.amountToPay) + + } + .padding() + .navigationBarTitle("Transaction Details", displayMode: .inline) } } diff --git a/reciept bill splitter/GroupDetailView.swift b/reciept bill splitter/GroupDetailView.swift index 3e39ee7..021c92f 100644 --- a/reciept bill splitter/GroupDetailView.swift +++ b/reciept bill splitter/GroupDetailView.swift @@ -121,7 +121,7 @@ struct GroupDetailView: View { let newTransaction = Transaction(transaction_id: "", itemList: transactionItems, itemBidders: [:], name: scanReceipt.title ?? "Untitled Transaction", isCompleted: false, dateCreated: nil) - await DatabaseAPI.createTransaction(transactionData: tempTransaction, groupID: selectedGroup.groupID) + await DatabaseAPI.createTransaction(transactionData: newTransaction, groupID: selectedGroup.groupID) } private func loadTransactions() async { diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index cf1537d..69be849 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -41,34 +41,7 @@ struct HomeView: View { Spacer() - Button("Transfer Money") { - paymentManager.transferMoney(amount: 1000, destinationAccountId: "acct_1Ovoc6QQyo8likZn") - } - - Button("Collect Payment") { - paymentManager.fetchPaymentDataAndPrepareSheet(uid: userViewModel.user_id, amount: 1000) - } - - VStack { - if let paymentSheet = paymentManager.paymentSheet { - PaymentSheet.PaymentButton(paymentSheet: paymentSheet, onCompletion: paymentManager.onPaymentCompletion) { - Text("Buy") - } - } else { - Text("Loading…") - } - - if let result = paymentManager.paymentResult { - switch result { - case .completed: - Text("Payment complete") - case .failed(let error): - Text("Payment failed: \(error.localizedDescription)") - case .canceled: - Text("Payment canceled.") - } - } - } + Menu { Button("Create Group") { @@ -97,7 +70,7 @@ struct HomeView: View { .navigationDestination(isPresented: $isJoiningGroup) { JoinGroupView() } - BottomToolbar(isLoggedIn: $isLoggedIn).environmentObject(paymentManager) + BottomToolbar(isLoggedIn: $isLoggedIn).environmentObject(paymentManager).environmentObject(userViewModel) } .navigationTitle("Home") .alert(isPresented: $showInfoAlert) { @@ -165,6 +138,7 @@ private func listenToTransactionsForGroup(groupId: String) { struct BottomToolbar: View { @EnvironmentObject var paymentManager: PaymentManager // Ensure this is passed down from the parent view + @EnvironmentObject var userViewModel: UserViewModel // Ensure this is passed down from the parent view @Binding var isLoggedIn: Bool @@ -173,7 +147,7 @@ struct BottomToolbar: View { HStack(spacing: 0.2) { ToolbarItem(iconName: "person.2", text: "Friends", destination: AnyView(FriendsView())) //ToolbarItem(iconName: "person.3", text: "Home", destination: AnyView(HomeView())) - ToolbarItem(iconName: "bolt", text: "Activate Transactions", destination: AnyView(AllAssignedTransactions())) + ToolbarItem(iconName: "bolt", text: "Activate Transactions", destination: AnyView(AllAssignedTransactions().environmentObject(userViewModel).environmentObject(paymentManager))) ToolbarItem(iconName: "person.crop.circle", text: "Accounts", destination: AnyView(AccountView(isLoggedIn: $isLoggedIn).environmentObject(paymentManager))) } .frame(height: 50) diff --git a/reciept bill splitter/LaunchScreenView.swift b/reciept bill splitter/LaunchScreenView.swift index d4785dd..9ea38ba 100644 --- a/reciept bill splitter/LaunchScreenView.swift +++ b/reciept bill splitter/LaunchScreenView.swift @@ -28,6 +28,8 @@ struct LaunchScreenView: View { HomeView(isLoggedIn: $isLoggedIn) } else { SignUpLogInView(isLoggedIn: $isLoggedIn) + .accentColor(.purple) // Match color with logo + } } .environmentObject(user) @@ -36,14 +38,32 @@ struct LaunchScreenView: View { VStack { VStack{ //Displaying Logo - Image(systemName: "wallet.pass.fill") - .font(.system(size: 80)) - .foregroundColor(.red) + ZStack { + Circle() + .fill(LinearGradient(gradient: Gradient(colors: [Color.blue.opacity(0.8), Color.purple.opacity(0.8)]), startPoint: .topLeading, endPoint: .bottomTrailing)) // Gradient circle color + .frame(width: 120, height: 120) // Adjust circle size as needed + .shadow(color: .black.opacity(0.5), radius: 5, x: 0, y: 2) // Add shadow for depth + + Image(systemName: "wallet.pass.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 80, height: 80) + .foregroundColor(.white) // Adjust icon color as needed + } + //Displaying App Name - Text("Bill Split") - .font(Font.custom("Baskerille-Bold", size: 26)) + Text("Wonder Wallet") + .font(.system(size: 26)) .foregroundColor(.black.opacity(0.80)) + .fontWeight(.light) + + // Displaying Slogan + Text("Scan, Split, Simplify.") + .font(.system(size: 16)) + .foregroundColor(.black.opacity(0.60)) + + } .scaleEffect(size) .opacity(opacity) diff --git a/reciept bill splitter/SignUpLogInView.swift b/reciept bill splitter/SignUpLogInView.swift index f1b92cf..07fbe75 100644 --- a/reciept bill splitter/SignUpLogInView.swift +++ b/reciept bill splitter/SignUpLogInView.swift @@ -24,7 +24,29 @@ struct SignUpLogInView: View { var body: some View { NavigationStack { - VStack { + VStack { + Text("Wonder Wallet") + .font(.system(size: 30)) + .foregroundColor(.black.opacity(0.80)) + .fontWeight(.light) + .padding() + + ZStack { + Circle() + .fill(LinearGradient(gradient: Gradient(colors: [Color.blue.opacity(0.8), Color.purple.opacity(0.8)]), startPoint: .topLeading, endPoint: .bottomTrailing)) + .frame(width: 80, height: 80) // Adjust circle size as needed + .shadow(color: .black.opacity(0.5), radius: 3, x: 0, y: 2) // Adjust shadow radius as needed + + Image(systemName: "wallet.pass.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 50, height: 50) // Adjust icon size as needed + .foregroundColor(.white) // Adjust icon color as needed + } + .frame(width: 100, height: 100) // Adjust total size of the icon + .padding() + + TextField("Email", text: $email) .padding() .background(Color.gray.opacity(0.2)) @@ -87,12 +109,13 @@ struct SignUpLogInView: View { } label: { Text("Login") - .foregroundColor(.white) + .font(.headline) + .foregroundColor(.white) // Set text color to white .padding() .frame(maxWidth: .infinity) - .background(Color.blue) - .cornerRadius(8.0) - .padding(.horizontal, 20) + .background(LinearGradient(gradient: Gradient(colors: [Color.blue.opacity(0.8), Color.purple.opacity(0.8)]), startPoint: .leading, endPoint: .trailing)) // Use gradient background + .cornerRadius(8) // Round the corners of the button + .padding(.horizontal) // Add horizontal padding } Button { @@ -108,6 +131,9 @@ struct SignUpLogInView: View { .navigationDestination(isPresented: $isSignUpActive){ SignUpView(isLoggedIn: $isLoggedIn) } + .navigationDestination(isPresented: $isLoggedIn){ + HomeView(isLoggedIn: $isLoggedIn) + } } } diff --git a/reciept bill splitter/ViewModels/DatabaseAPI.swift b/reciept bill splitter/ViewModels/DatabaseAPI.swift index dd22320..da8b0c0 100644 --- a/reciept bill splitter/ViewModels/DatabaseAPI.swift +++ b/reciept bill splitter/ViewModels/DatabaseAPI.swift @@ -79,18 +79,22 @@ class DatabaseAPI { } let assignedTransactions = data["assignedTransaction"] as? [String : [String : Any]] ?? [:] - // Create new AssignedTransaction from list in DB and return it - for (transaction_id, dataDict) in assignedTransactions { - let ammountToPay = dataDict["ammountToPay"] as? Int ?? 0 - let associatedTransaction_id = transaction_id - let isPaid = dataDict["isPaid"] as? Bool ?? false - let transactionName = data["transactionName"] as? String ?? "Unknown Transaction Name" - let user_idToPay = data["user_idToPay"] as? String ?? "" - - let newAssignment = AssignedTransaction(transactionName: transactionName, associatedTransaction_id: associatedTransaction_id, user_idToPay: user_idToPay, isPaid: isPaid, amountToPay: ammountToPay) - - userAssignedTransactions.append(newAssignment) + // Iterate through each assigned transaction + for (transactionID, dataDict) in assignedTransactions { + if let amountToPay = dataDict["ammountToPay"] as? Double, // Note the spelling 'ammountToPay' matches your Firestore data + let isPaid = dataDict["isPaid"] as? Bool, + let transactionName = dataDict["transactionName"] as? String, + let user_idToPay = dataDict["user_idToPay"] as? String { + + // Using 'transactionID' from the iteration to set 'associatedTransaction_id' + let newAssignment = AssignedTransaction(transactionName: transactionName, associatedTransaction_id: transactionID, user_idToPay: user_idToPay, isPaid: isPaid, amountToPay: Int(amountToPay)) // Convert double to int representing cents + + userAssignedTransactions.append(newAssignment) + } else { + print("Missing data for assigned transaction") + } } + return userAssignedTransactions } @@ -99,7 +103,7 @@ class DatabaseAPI { } return nil } - + static func createGroup(groupName: String) async -> Void { guard let user = Auth.auth().currentUser else { print("User Does not exist") @@ -376,7 +380,29 @@ class DatabaseAPI { return nil } - + static func markTransactionAsPaid(assignedTransactionId: String, completion: @escaping (Error?) -> Void) { + guard let user = Auth.auth().currentUser else { + print("User does not exist") + completion(NSError(domain: "AuthError", code: -1, userInfo: [NSLocalizedDescriptionKey: "User not authenticated"])) + return + } + + // Reference to the user's document + let userRef = db.collection("users").document(user.uid) + + // Prepare the update for the specific assigned transaction + let updateField = "assignedTransaction.\(assignedTransactionId).isPaid" + + // Perform the update + userRef.updateData([updateField: true]) { error in + if let error = error { + print("Error marking transaction as paid: \(error.localizedDescription)") + } else { + print("Transaction marked as paid successfully.") + } + completion(error) + } + } static func assignAllGroupMembersPayment(transaction_id: String) async -> Void { guard let _ = Auth.auth().currentUser else { print("User Does not exist") diff --git a/reciept bill splitter/ViewModels/PaymentManager.swift b/reciept bill splitter/ViewModels/PaymentManager.swift index c370ca3..7f663d2 100644 --- a/reciept bill splitter/ViewModels/PaymentManager.swift +++ b/reciept bill splitter/ViewModels/PaymentManager.swift @@ -144,7 +144,7 @@ class PaymentManager: ObservableObject { }else {print("error creating link2")} } } - func transferMoney(amount: Int, destinationAccountId: String) { + func transferMoney(amount: Int, destinationAccountId: String, assignedTransactionId: String) { let functions = Functions.functions() functions.httpsCallable("createTransfer").call(["amount": amount, "destinationAccountId": destinationAccountId]) { result, error in if let error = error { @@ -153,11 +153,21 @@ class PaymentManager: ObservableObject { } if let transferId = (result?.data as? [String: Any])?["transferId"] as? String { print("Transfer successful, transferId: \(transferId)") + // Mark the transaction as paid + DatabaseAPI.markTransactionAsPaid(assignedTransactionId: assignedTransactionId) { error in + if let error = error { + print("Error marking transaction as paid: \(error.localizedDescription)") + } else { + print("Transaction successfully marked as paid") + // Here you can update any UI or state to reflect the payment status + } + } } else { print("Transfer failed") } } } + func getStripeConnectAccountIdByEmail(email: String, completion: @escaping (String?, Error?) -> Void) { let customersRef = Firestore.firestore().collection("customers") customersRef.whereField("email", isEqualTo: email).getDocuments { (querySnapshot, error) in From 1e56766abedad606675b36cde8dd4a06041683d2 Mon Sep 17 00:00:00 2001 From: diegomtz5 Date: Fri, 22 Mar 2024 12:36:50 -0700 Subject: [PATCH 14/20] fixed some bugs --- .../AssignedTransactionDetails.swift | 3 +-- reciept bill splitter/GroupDetailView.swift | 27 +++++++++---------- reciept bill splitter/HomeView.swift | 4 +-- reciept bill splitter/ScanReciept.swift | 9 +++++-- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/reciept bill splitter/AssignedTransactionDetails.swift b/reciept bill splitter/AssignedTransactionDetails.swift index 2fa54b7..24c425b 100644 --- a/reciept bill splitter/AssignedTransactionDetails.swift +++ b/reciept bill splitter/AssignedTransactionDetails.swift @@ -12,8 +12,7 @@ struct AssignedTransactionDetails: View { VStack { Text("Transaction Name: \(assignedTransaction.transactionName)") .font(.headline) - Text("Transaction Name: \(assignedTransaction.associatedTransaction_id)") - .font(.headline) + Text("Amount to Pay: $\(String(format: "%.2f", Double(assignedTransaction.amountToPay) / 100))") .font(.subheadline) diff --git a/reciept bill splitter/GroupDetailView.swift b/reciept bill splitter/GroupDetailView.swift index 021c92f..997a6f2 100644 --- a/reciept bill splitter/GroupDetailView.swift +++ b/reciept bill splitter/GroupDetailView.swift @@ -27,7 +27,7 @@ struct GroupDetailView: View { @State private var selectedTransactionID = "" @State var isAlert = false - + @State var scannedItems: [ReceiptItem] = [] @StateObject var scanReceipt = ScanReceipt() @EnvironmentObject var user: UserViewModel @@ -65,21 +65,21 @@ struct GroupDetailView: View { if selectedGroup.owner_id == user.user_id { Button("Open Camera") { - Task { - await createTransaction() - } + isCameraPresented = true } .sheet(isPresented: $isCameraPresented) { CameraView(isPresented: $isCameraPresented, selectedImage: $selectedImage, isTaken: $isTaken) } .onChange(of: isTaken) { - if let imageToScan = selectedImage { + if isTaken, let imageToScan = selectedImage { Task { - await scanReceipt.scanReceipt(image: imageToScan) + self.scannedItems = await scanReceipt.scanReceipt(image: imageToScan) + await createTransaction() + isTaken = false // Reset the flag after transaction creation + await loadTransactions() } } - isTaken = false // Reset the flag } } @@ -95,11 +95,11 @@ struct GroupDetailView: View { } } .onChange(of: scanReceipt.isScanning) { - if !scanReceipt.isScanning { - Task { - await createTransaction() - } - } +// if !scanReceipt.isScanning { +// Task { +// await createTransaction() +// } +// } } .onAppear { formatter.dateStyle = .short @@ -115,9 +115,8 @@ struct GroupDetailView: View { } private func createTransaction() async { - let scannedItems = scanReceipt.receiptItems // Assume these are the scanned receipt items + let scannedItems = scannedItems// Assume these are the scanned receipt items let transactionItems = scannedItems.map { Item(priceInCents: Int($0.price * 100), name: $0.name) } - let tempTransaction = Transaction(transaction_id: "", itemList: [], itemBidders: [:], name: scanReceipt.title ?? "Untitled Transaction", isCompleted: false, dateCreated: nil) let newTransaction = Transaction(transaction_id: "", itemList: transactionItems, itemBidders: [:], name: scanReceipt.title ?? "Untitled Transaction", isCompleted: false, dateCreated: nil) diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index 69be849..02cebdd 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -83,13 +83,13 @@ struct HomeView: View { .onAppear { Task { await userViewModel.getUserData() + await userViewModel.updateCanGetPaidStatus() } } } private func assignUsersTransaction() { Task{ await userViewModel.getUserData() - await userViewModel.updateCanGetPaidStatus() } } @@ -145,7 +145,7 @@ struct BottomToolbar: View { var body: some View { HStack(spacing: 0.2) { - ToolbarItem(iconName: "person.2", text: "Friends", destination: AnyView(FriendsView())) + //ToolbarItem(iconName: "person.2", text: "Friends", destination: AnyView(FriendsView())) //ToolbarItem(iconName: "person.3", text: "Home", destination: AnyView(HomeView())) ToolbarItem(iconName: "bolt", text: "Activate Transactions", destination: AnyView(AllAssignedTransactions().environmentObject(userViewModel).environmentObject(paymentManager))) ToolbarItem(iconName: "person.crop.circle", text: "Accounts", destination: AnyView(AccountView(isLoggedIn: $isLoggedIn).environmentObject(paymentManager))) diff --git a/reciept bill splitter/ScanReciept.swift b/reciept bill splitter/ScanReciept.swift index fbb48b5..ffe9c79 100644 --- a/reciept bill splitter/ScanReciept.swift +++ b/reciept bill splitter/ScanReciept.swift @@ -41,7 +41,7 @@ class ScanReceipt: ObservableObject { private var receiptItemsTemp: [ReceiptItem] = [] @Published var uiImage: UIImage? private var tempimage: UIImage? - func scanReceipt(image: UIImage) async { + func scanReceipt(image: UIImage) async -> [ReceiptItem]{ Task { @MainActor in self.isScanning = true self.receiptItems = [] @@ -69,8 +69,13 @@ class ScanReceipt: ObservableObject { self.uiImage = tempimage self.title = titleTemp self.isScanning = false - + } + if let tax = finalTax { + finalItems.append(tax) + } + return finalItems + } private func runModel(image: UIImage) async { From b5f290ef450584bf1c99a60d57ce00b8e68a7614 Mon Sep 17 00:00:00 2001 From: diegomtz5 Date: Fri, 22 Mar 2024 12:55:11 -0700 Subject: [PATCH 15/20] Users can bid on items --- reciept bill splitter/TransactionView.swift | 52 +++++++++++++++++---- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/reciept bill splitter/TransactionView.swift b/reciept bill splitter/TransactionView.swift index 7f142f9..10b7791 100644 --- a/reciept bill splitter/TransactionView.swift +++ b/reciept bill splitter/TransactionView.swift @@ -22,21 +22,28 @@ struct TransactionView: View { // index of the bidding members and get the dictiionary count inside List { ForEach(transaction.itemList.indices, id: \.self) { index in - let bidders = transaction.itemBidders[String(index)] ?? [] - let biddersCount = bidders.count - let isCurrentUserBidding = bidders.contains(user.user_id) + let isCurrentUserBidding = transaction.itemBidders[String(index)]?.contains(user.user_id) ?? false HStack { Text("\(transaction.itemList[index].name): $\(String(format: "%.2f", Double(transaction.itemList[index].priceInCents) / 100))") - .foregroundColor(isCurrentUserBidding ? .blue : .primary) // Change color if the current user is bidding Spacer() - // Display the number of bidders for each item - Text("Bidders: \(biddersCount)") - .foregroundColor(.gray) - .font(.subheadline) + Button(action: { + Task { + await bidOnItem(itemIndex: index, transactionID: transaction.transaction_id, userID: user.user_id) + transactionData = await DatabaseAPI.grabTransaction(transaction_id: selectedTransactionId) + + } + }) { + Text(isCurrentUserBidding ? "Unbid" : "Bid") + .foregroundColor(.white) + .padding() + .background(isCurrentUserBidding ? Color.red : Color.green) + .cornerRadius(10) + } } } } + Text("Your Total Contribution: $\(String(format: "%.2f", calculateUserTotalContribution(transaction: transaction, userID: user.user_id)))") .fontWeight(.bold) @@ -96,6 +103,35 @@ struct TransactionView: View { return totalContribution } + func bidOnItem(itemIndex: Int, transactionID: String, userID: String) async { + let transactionRef = Firestore.firestore().collection("transactions").document(transactionID) + + do { + let document = try await transactionRef.getDocument() + if document.exists, var transactionData = document.data() { + var itemBidders = transactionData["itemBidders"] as? [String: [String]] ?? [:] + var bidders = itemBidders[String(itemIndex)] ?? [] + + if let index = bidders.firstIndex(of: userID) { + // User is already bidding, remove their bid + bidders.remove(at: index) + } else { + // Add user's bid + bidders.append(userID) + } + + // Update the bidders list for the item + itemBidders[String(itemIndex)] = bidders + transactionData["itemBidders"] = itemBidders + + // Update the transaction document + try await transactionRef.updateData(transactionData) + } + } catch { + print("Error updating transaction: \(error)") + } + } + // func parseTransactionData(_ data: [String: Any]) -> Transaction { // // Parse the data into your Transaction model // // Example: From d7c852ed572902240aca2f735d5b43b34c8a62c3 Mon Sep 17 00:00:00 2001 From: diegomtz5 Date: Fri, 22 Mar 2024 13:54:52 -0700 Subject: [PATCH 16/20] Users can edit transaction name --- .../project.pbxproj | 4 - reciept bill splitter/FriendsView.swift | 97 ------------------- reciept bill splitter/TransactionView.swift | 86 +++++++++++++--- 3 files changed, 71 insertions(+), 116 deletions(-) delete mode 100644 reciept bill splitter/FriendsView.swift diff --git a/reciept bill splitter.xcodeproj/project.pbxproj b/reciept bill splitter.xcodeproj/project.pbxproj index 88e3647..2c031e6 100644 --- a/reciept bill splitter.xcodeproj/project.pbxproj +++ b/reciept bill splitter.xcodeproj/project.pbxproj @@ -27,7 +27,6 @@ 36788E3F2BA50F22006925A6 /* GroupDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36788E3E2BA50F22006925A6 /* GroupDetailView.swift */; }; 36E9BE642B86ACE00020BE12 /* SignUpLogInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE632B86ACE00020BE12 /* SignUpLogInView.swift */; }; 36E9BE662B86AD1C0020BE12 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE652B86AD1C0020BE12 /* HomeView.swift */; }; - 36E9BE682B86AD3F0020BE12 /* FriendsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE672B86AD3F0020BE12 /* FriendsView.swift */; }; 36E9BE6C2B86AD5D0020BE12 /* AllAssignedTransactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE6B2B86AD5D0020BE12 /* AllAssignedTransactions.swift */; }; 36E9BE6E2B86AD6E0020BE12 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE6D2B86AD6E0020BE12 /* SettingsView.swift */; }; 36E9BE702B86AD8D0020BE12 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E9BE6F2B86AD8D0020BE12 /* SplitView.swift */; }; @@ -69,7 +68,6 @@ 36788E422BA7EEB3006925A6 /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = ""; }; 36E9BE632B86ACE00020BE12 /* SignUpLogInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpLogInView.swift; sourceTree = ""; }; 36E9BE652B86AD1C0020BE12 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; - 36E9BE672B86AD3F0020BE12 /* FriendsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendsView.swift; sourceTree = ""; }; 36E9BE6B2B86AD5D0020BE12 /* AllAssignedTransactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllAssignedTransactions.swift; sourceTree = ""; }; 36E9BE6D2B86AD6E0020BE12 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 36E9BE6F2B86AD8D0020BE12 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; @@ -144,7 +142,6 @@ 36E9BE752B86CAEA0020BE12 /* AddFriendView.swift */, 32B1A4C22B828F9D00A20FDD /* Assets.xcassets */, 32847F2B2B8D96D50061449F /* ContentView.swift */, - 36E9BE672B86AD3F0020BE12 /* FriendsView.swift */, 32E5E1882B8DA66C005BF4F4 /* GoogleService-Info.plist */, 36788E3E2BA50F22006925A6 /* GroupDetailView.swift */, 36E9BE6B2B86AD5D0020BE12 /* AllAssignedTransactions.swift */, @@ -292,7 +289,6 @@ 32847F282B8D96B70061449F /* UIImageExtension.swift in Sources */, 36E9BE6C2B86AD5D0020BE12 /* AllAssignedTransactions.swift in Sources */, 36E9BE6E2B86AD6E0020BE12 /* SettingsView.swift in Sources */, - 36E9BE682B86AD3F0020BE12 /* FriendsView.swift in Sources */, 36788E3F2BA50F22006925A6 /* GroupDetailView.swift in Sources */, 32847F2A2B8D96C60061449F /* ScanReciept.swift in Sources */, 32847F2C2B8D96D50061449F /* ContentView.swift in Sources */, diff --git a/reciept bill splitter/FriendsView.swift b/reciept bill splitter/FriendsView.swift deleted file mode 100644 index b5a55ff..0000000 --- a/reciept bill splitter/FriendsView.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// FriendsView.swift -// reciept bill splitter -// -// Created by Josh Vu on 2/21/24. -// - -import SwiftUI - -struct FriendsView: View { - @State private var isAddFriendsActive : Bool = false - @State private var searchText: String = "" - @State private var friends: [(String, String)] = [ - ("John", "john_doe"), - ("Jane", "jane_smith"), - ("Alice", "alice_wonder"), - ("Bob", "bob_jones"), - ("Emily", "emily_green"), - ("Michael", "michael_brown"), - ] - @State private var isContextMenuVisible = false // State variable to control visibility of context menu - @State private var friendToDelete: (String, String)? // Track the friend to delete - @State private var showingDeleteAlert = false // State variable to control visibility of delete confirmation alert - - - var body: some View { - NavigationStack{ - VStack { - TextField("Search", text: $searchText) - .autocapitalization(.none) // Disable automatic capitalization - .padding() - .background(Color(.systemGray6)) - .cornerRadius(8) - .padding(.horizontal) - List(friends.filter({ searchText.isEmpty ? true : $0.0.contains(searchText) || $0.1.contains(searchText) }), id: \.0) { friend, username in - HStack { - Image(systemName: "person.circle") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 50, height: 50) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text(friend) - .font(.headline) - Text("\(username)") - .font(.subheadline) - .foregroundColor(.secondary) - } - Spacer() // Add spacer to push button to the right - - Button(action: { - friendToDelete = (friend, username) // Set the friend to delete - showingDeleteAlert = true - }) { - Image(systemName: "trash") - .font(.headline) - .foregroundColor(.black) - } - } - } - .alert(isPresented: $showingDeleteAlert) { - Alert(title: Text("Delete Friend"), message: Text("Are you sure you want to delete \(friendToDelete?.1 ?? "")?"), primaryButton: .destructive(Text("Yes")) { - // Handle delete action here - if let friendToDelete = friendToDelete { - deleteFriend(friendToDelete) - } - }, secondaryButton: .cancel(Text("Cancel"))) - } - Button(action: { - isAddFriendsActive = true // Set isSignUpActive to true when button is tapped - }) { - Text("Add Friends") - .foregroundColor(.blue) - .padding(.horizontal, 20) - } - Rectangle() - .foregroundColor(Color(.systemGray6)) - .frame(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height/15) - } - .navigationTitle("Friends") - .navigationDestination(isPresented: $isAddFriendsActive){ - AddFriendView() - } - } - } - func deleteFriend(_ friend: (String, String)) { - // Implement your logic to delete the friend here - if let index = friends.firstIndex(where: { $0 == friend }) { - friends.remove(at: index) - } - } -} - -#Preview { - FriendsView() -} diff --git a/reciept bill splitter/TransactionView.swift b/reciept bill splitter/TransactionView.swift index 10b7791..e8dc4fd 100644 --- a/reciept bill splitter/TransactionView.swift +++ b/reciept bill splitter/TransactionView.swift @@ -11,14 +11,41 @@ struct TransactionView: View { @Binding var groupData: Group @State var transactionData: Transaction? - + @State private var isEditingName = false + @State private var editedName: String = "" + var body: some View { VStack { if let transaction = transactionData { let totalSpent = transaction.itemList.map { Double($0.priceInCents) / 100 }.reduce(0, +) - Text(transaction.name) - .font(.title) + if isEditingName && groupData.owner_id == user.user_id { + TextField("Transaction Name", text: $editedName) + .textFieldStyle(RoundedBorderTextFieldStyle()) + Button("Save") { + Task { + await saveTransactionName(transactionId: transaction.transaction_id, newName: editedName) + isEditingName = false + } + } + .padding() + .background(Color.blue) + .foregroundColor(Color.white) + .cornerRadius(10) + } else { + Text(transaction.name) + .font(.title) + if groupData.owner_id == user.user_id { + + Button(action: { + isEditingName = true + editedName = transaction.name + }) { + Image(systemName: isEditingName ? "checkmark.circle.fill" : "pencil.circle.fill") + .foregroundColor(isEditingName ? .green : .blue) + } + } + } // index of the bidding members and get the dictiionary count inside List { ForEach(transaction.itemList.indices, id: \.self) { index in @@ -40,6 +67,7 @@ struct TransactionView: View { .background(isCurrentUserBidding ? Color.red : Color.green) .cornerRadius(10) } + } } } @@ -131,18 +159,46 @@ struct TransactionView: View { print("Error updating transaction: \(error)") } } + func addItemToTransaction(transactionId: String, newItem: Item) { + let transactionRef = Firestore.firestore().collection("transactions").document(transactionId) + + transactionRef.getDocument { (document, error) in + if let document = document, document.exists { + var currentItems = document.get("items") as? [[String: Any]] ?? [] + let newItemDict = ["name": newItem.name, "priceInCents": newItem.priceInCents] + currentItems.append(newItemDict) + + transactionRef.updateData(["items": currentItems]) + } + } + } + func setItemPriceToZero(transactionId: String, itemIndex: Int) { + let transactionRef = Firestore.firestore().collection("transactions").document(transactionId) + + transactionRef.getDocument { (document, error) in + if let document = document, document.exists { + var currentItems = document.get("items") as? [[String: Any]] ?? [] + if itemIndex < currentItems.count { + // Set the item's price to 0 instead of removing it + currentItems[itemIndex]["priceInCents"] = 0 + transactionRef.updateData(["items": currentItems]) + } + } + } + } + func saveTransactionName(transactionId: String, newName: String) async { + let transactionRef = Firestore.firestore().collection("transactions").document(transactionId) -// func parseTransactionData(_ data: [String: Any]) -> Transaction { -// // Parse the data into your Transaction model -// // Example: -// let name = data["name"] as? String ?? "Unknown" -// let itemList = data["itemList"] as? [[String: Any]] ?? [] -// let itemObjects = itemList.map { itemData -> Item in -// let name = itemData["name"] as? String ?? "Item" -// let priceInCents = itemData["priceInCents"] as? Int ?? 0 -// return Item(priceInCents: priceInCents, name: name) -// } -// return Transaction(name: name, itemList: itemObjects, ...) -// } + do { + try await transactionRef.updateData(["name": newName]) + // Update local transaction data to reflect the new name + if var transaction = transactionData { + transaction.name = newName + transactionData = transaction + } + } catch { + print("Error updating transaction name: \(error)") + } + } } From 5802ad90b0e4ebee8bdec4c3156194ade6be8e4f Mon Sep 17 00:00:00 2001 From: diegomtz5 Date: Fri, 22 Mar 2024 14:33:32 -0700 Subject: [PATCH 17/20] Minor Fix --- reciept bill splitter/GroupDetailView.swift | 24 +++------------------ reciept bill splitter/HomeView.swift | 10 ++------- reciept bill splitter/TransactionView.swift | 4 ++++ 3 files changed, 9 insertions(+), 29 deletions(-) diff --git a/reciept bill splitter/GroupDetailView.swift b/reciept bill splitter/GroupDetailView.swift index 997a6f2..4840dc7 100644 --- a/reciept bill splitter/GroupDetailView.swift +++ b/reciept bill splitter/GroupDetailView.swift @@ -64,6 +64,7 @@ struct GroupDetailView: View { } if selectedGroup.owner_id == user.user_id { + Button("Open Camera") { isCameraPresented = true @@ -94,13 +95,7 @@ struct GroupDetailView: View { MembersListView(members: selectedGroup.members) } } - .onChange(of: scanReceipt.isScanning) { -// if !scanReceipt.isScanning { -// Task { -// await createTransaction() -// } -// } - } + .onAppear { formatter.dateStyle = .short @@ -139,20 +134,7 @@ struct GroupDetailView: View { } } -/*struct MembersListView: View { - let members: [GroupMember] - - var body: some View { - List { - ForEach(members, id: \.id) { member in - Text(member.id) - } - } - .onAppear { - print("Members: \(members)") - } - } -}*/ + struct MembersListView: View { let members: [GroupMember] diff --git a/reciept bill splitter/HomeView.swift b/reciept bill splitter/HomeView.swift index 02cebdd..61242f2 100644 --- a/reciept bill splitter/HomeView.swift +++ b/reciept bill splitter/HomeView.swift @@ -127,14 +127,8 @@ private func listenToTransactionsForGroup(groupId: String) { } } } - - - } - - - - + } struct BottomToolbar: View { @EnvironmentObject var paymentManager: PaymentManager // Ensure this is passed down from the parent view @@ -147,7 +141,7 @@ struct BottomToolbar: View { HStack(spacing: 0.2) { //ToolbarItem(iconName: "person.2", text: "Friends", destination: AnyView(FriendsView())) //ToolbarItem(iconName: "person.3", text: "Home", destination: AnyView(HomeView())) - ToolbarItem(iconName: "bolt", text: "Activate Transactions", destination: AnyView(AllAssignedTransactions().environmentObject(userViewModel).environmentObject(paymentManager))) + ToolbarItem(iconName: "dollarsign", text: "Pay Transactions", destination: AnyView(AllAssignedTransactions().environmentObject(userViewModel).environmentObject(paymentManager))) ToolbarItem(iconName: "person.crop.circle", text: "Accounts", destination: AnyView(AccountView(isLoggedIn: $isLoggedIn).environmentObject(paymentManager))) } .frame(height: 50) diff --git a/reciept bill splitter/TransactionView.swift b/reciept bill splitter/TransactionView.swift index e8dc4fd..04c0d38 100644 --- a/reciept bill splitter/TransactionView.swift +++ b/reciept bill splitter/TransactionView.swift @@ -13,6 +13,7 @@ struct TransactionView: View { @State var transactionData: Transaction? @State private var isEditingName = false @State private var editedName: String = "" + @Environment(\.dismiss) var dismiss // Use the dismiss environment value var body: some View { VStack { @@ -83,6 +84,9 @@ struct TransactionView: View { Button("Complete Transaction") { Task { await DatabaseAPI.toggleGroupTransactionsCompletion(transactionID: transaction.transaction_id, completion: true) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // Optional delay to allow users to see the completion message + dismiss() // Dismiss the view + } } } } From 6f7c282148cf908e4d22881d1648e9e2fa246a5d Mon Sep 17 00:00:00 2001 From: diegomtz5 <141683174+diegomtz5@users.noreply.github.com> Date: Fri, 22 Mar 2024 14:48:11 -0700 Subject: [PATCH 18/20] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index dba8c76..029838d 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,13 @@ In the home view, the user can view their account that allow them to update thei ## Special Instructions Our app uses the phone camera in order to function and as a result will need to be tested with an actual phone. +To fill out Stripe's onboarding information, these values can be used. +Auto-fill 000-000-0000 as the test phone number and 000-000 as the SMS code when prompted (Express) +Use these personal ID numbers for individual.id_number or the id_number attribute on the Person object to trigger certain verification conditions. +000000000 Successful verification. 0000 also works for SSN last 4 verification. +Use these business tax ID numbers for company.tax_id to trigger certain verification conditions. +000000000 Successful verification. +Fill out website information https://accessible.stripe.com +Address address_full_match You must pass in legitimate values for the city, state, and postal_code arguments. +Payment Methods +Number: 4242424242424242, CVC: Any 3 digits, Date: Any future date From dac0346ac58e75162ece850ea03309a3bec81738 Mon Sep 17 00:00:00 2001 From: diegomtz5 <141683174+diegomtz5@users.noreply.github.com> Date: Fri, 22 Mar 2024 14:49:38 -0700 Subject: [PATCH 19/20] Update README.md --- README.md | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 029838d..c929d98 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,27 @@ In the home view, the user can view their account that allow them to update thei ## Special Instructions -Our app uses the phone camera in order to function and as a result will need to be tested with an actual phone. -To fill out Stripe's onboarding information, these values can be used. -Auto-fill 000-000-0000 as the test phone number and 000-000 as the SMS code when prompted (Express) -Use these personal ID numbers for individual.id_number or the id_number attribute on the Person object to trigger certain verification conditions. -000000000 Successful verification. 0000 also works for SSN last 4 verification. -Use these business tax ID numbers for company.tax_id to trigger certain verification conditions. -000000000 Successful verification. -Fill out website information https://accessible.stripe.com -Address address_full_match You must pass in legitimate values for the city, state, and postal_code arguments. -Payment Methods -Number: 4242424242424242, CVC: Any 3 digits, Date: Any future date +### App Testing Requirements +- **Camera Usage**: Our application requires access to the phone's camera to operate correctly. Ensure testing is on an actual mobile device with camera capabilities. + +### Stripe Onboarding Test Data +Use the following dummy information for testing the Stripe onboarding process: + +- **Test Phone Number**: Enter `000-000-0000` for any phone number fields. +- **SMS Code**: Use `000-000` when prompted for an SMS verification code. +- **Personal ID Numbers**: + - For successful individual verification, use `000000000` for the `individual.id_number` or the `id_number` attribute on the `Person` object. For SSN's last 4 digits, `0000` will work. +- **Business Tax ID Numbers**: + - Input `000000000` in the `company.tax_id` field for successful company verification. +- **Website Information**: Use `https://accessible.stripe.com` for website-related fields. +- **Address Validation**: + - Input legitimate values for `city`, `state`, and `postal_code` in the `address_full_match`. + +### Payment Method Simulation +For payment testing, use the following mock credit card details: +- **Card Number**: `4242424242424242` +- **CVC**: Any 3-digit number +- **Expiration Date**: Any future date + +Note: These values are for testing purposes only. Switch to real data for production. + From d30aae2c57e9504dddb0586bbbd7910730dadabf Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Sun, 9 Jun 2024 15:49:54 -0700 Subject: [PATCH 20/20] Update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index c929d98..3d2efae 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ +## What is our app +Our App is a simple bill splitter app that intents to use AI so that the user can take a picture of their reciept and split each item with all other members. + +## Use Flow +Users will first login to their account and from there, they are able to create or join a group. + +Groups will have all the users who are planning on splitting a bill. + +The creator of the group will be able to take a picture of the reciept and automatically display a listing of all items, and their costs. +All members of the group will be assigned to each item which will be their split that they pay to the owner of the group. + +Users will use Strip to make their payments. ## Specifications Our code is structured where, we have a 2 files that help handle our database interaction, and our User specific data.