diff --git a/Splito/UI/Home/Expense/AddExpenseView.swift b/Splito/UI/Home/Expense/AddExpenseView.swift index 6bddb77de..547ac6dde 100644 --- a/Splito/UI/Home/Expense/AddExpenseView.swift +++ b/Splito/UI/Home/Expense/AddExpenseView.swift @@ -34,10 +34,9 @@ struct AddExpenseView: View { .scrollIndicators(.hidden) .scrollBounceBehavior(.basedOnSize) - AddExpenseFooterView(date: $viewModel.expenseDate, showImagePickerOptions: $viewModel.showImagePickerOptions, - expenseImage: viewModel.expenseImage, expenseImageUrl: viewModel.expenseImageUrl, - handleNoteBtnTap: viewModel.handleNoteBtnTap, handleExpenseImageTap: viewModel.handleExpenseImageTap, - handleActionSelection: viewModel.handleActionSelection(_:)) + FooterView(date: $viewModel.expenseDate, showImagePickerOptions: $viewModel.showImagePickerOptions, + image: viewModel.expenseImage, imageUrl: viewModel.expenseImageUrl, handleNoteBtnTap: viewModel.handleNoteBtnTap, + handleImageTap: viewModel.handleExpenseImageTap, handleActionSelection: viewModel.handleActionSelection(_:)) } } .background(surfaceColor) @@ -204,16 +203,16 @@ private struct ExpenseDetailRow: View { } } -struct AddExpenseFooterView: View { +struct FooterView: View { @Binding var date: Date @Binding var showImagePickerOptions: Bool - let expenseImage: UIImage? - let expenseImageUrl: String? + let image: UIImage? + let imageUrl: String? let handleNoteBtnTap: (() -> Void) - let handleExpenseImageTap: (() -> Void) + let handleImageTap: (() -> Void) let handleActionSelection: ((ActionsOfSheet) -> Void) var body: some View { @@ -226,14 +225,14 @@ struct AddExpenseFooterView: View { DatePickerView(date: $date) - ExpenseImagePickerView(image: expenseImage, imageUrl: expenseImageUrl, handleImageBtnTap: handleExpenseImageTap) + ExpenseImagePickerView(image: image, imageUrl: imageUrl, handleImageBtnTap: handleImageTap) NoteButtonView(handleNoteBtnTap: handleNoteBtnTap) } .padding(.vertical, 12) .padding(.horizontal, 16) .confirmationDialog("", isPresented: $showImagePickerOptions, titleVisibility: .hidden) { - ImagePickerOptionsView(image: expenseImage, imageUrl: expenseImageUrl, handleActionSelection: handleActionSelection) + ImagePickerOptionsView(image: image, imageUrl: imageUrl, handleActionSelection: handleActionSelection) } } } diff --git a/Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift b/Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift index d35daddba..dd6d47e9f 100644 --- a/Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift +++ b/Splito/UI/Home/Expense/Expense Detail/ExpenseDetailsView.swift @@ -43,7 +43,7 @@ struct ExpenseDetailsView: View { } if let note = viewModel.expense?.note, !note.isEmpty { - ExpenseNoteView(note: note, handleNoteTap: viewModel.handleNoteTap) + NoteContentView(note: note, handleNoteTap: viewModel.handleNoteTap) } VSpacer(24) @@ -207,7 +207,7 @@ private struct ExpenseInfoView: View { } } -struct ExpenseNoteView: View { +struct NoteContentView: View { let note: String diff --git a/Splito/UI/Home/Expense/Note/AddNoteViewModel.swift b/Splito/UI/Home/Expense/Note/AddNoteViewModel.swift index c570a7af2..d9527841e 100644 --- a/Splito/UI/Home/Expense/Note/AddNoteViewModel.swift +++ b/Splito/UI/Home/Expense/Note/AddNoteViewModel.swift @@ -10,17 +10,19 @@ import BaseStyle import Foundation class AddNoteViewModel: BaseViewModel, ObservableObject { - + + @Inject private var userRepository: UserRepository @Inject private var expenseRepository: ExpenseRepository - + @Inject private var transactionRepository: TransactionRepository + @Published var note: String @Published private(set) var showLoader: Bool = false - + private let group: Groups? private let expense: Expense? private let payment: Transactions? private let handleSaveNoteTap: ((String) -> Void)? - + init(group: Groups?, expense: Expense? = nil, payment: Transactions? = nil, note: String, handleSaveNoteTap: ((String) -> Void)? = nil) { self.group = group self.expense = expense @@ -29,40 +31,80 @@ class AddNoteViewModel: BaseViewModel, ObservableObject { self.handleSaveNoteTap = handleSaveNoteTap super.init() } - + // MARK: - User Actions func showSaveFailedError() { self.showToastFor(toast: ToastPrompt(type: .error, title: "Oops", message: "Failed to save note.")) } - + func handleSaveNoteAction() async -> Bool { if let handleSaveNoteTap { handleSaveNoteTap(note) return true } - - guard let expense, expense.note != note else { return true } - return await updateExpenseNote() + + if let expense, expense.note != note { + return await updateExpenseNote() + } else if let payment, payment.note != note { + return await updatePaymentNote() + } + + return true } - + private func updateExpenseNote() async -> Bool { guard let group, let expense else { return false } - + do { showLoader = true var updatedExpense = expense updatedExpense.note = note - + try await expenseRepository.updateExpense(group: group, expense: updatedExpense, oldExpense: expense, type: .expenseUpdated) NotificationCenter.default.post(name: .updateExpense, object: updatedExpense) - + showLoader = false - LogD("ExpenseAddNoteViewModel: \(#function) Expense note updated successfully.") + LogD("AddNoteViewModel: \(#function) Expense note updated successfully.") return true } catch { - LogE("ExpenseAddNoteViewModel: \(#function) Failed to update expense note: \(error).") + LogE("AddNoteViewModel: \(#function) Failed to update expense note: \(error).") showToastForError() return false } } + + private func updatePaymentNote() async -> Bool { + guard let group, let payment else { return false } + + do { + showLoader = true + let members = try await fetchMembers(payerId: payment.payerId, receiverId: payment.receiverId) + guard let members else { return false } + + var updatedPayment = payment + updatedPayment.note = note + try await transactionRepository.updateTransaction(group: group, transaction: updatedPayment, oldTransaction: payment, + members: members, type: .transactionUpdated) + NotificationCenter.default.post(name: .updateTransaction, object: updatedPayment) + + showLoader = false + LogD("AddNoteViewModel: \(#function) Payment note updated successfully.") + return true + } catch { + showLoader = false + LogE("AddNoteViewModel: \(#function) Failed to update payment note: \(error).") + showToastForError() + return false + } + } + + private func fetchMembers(payerId: String, receiverId: String) async throws -> (payer: AppUser, receiver: AppUser)? { + let payer = try await userRepository.fetchUserBy(userID: payerId) + let receiver = try await userRepository.fetchUserBy(userID: receiverId) + + if let payer, let receiver { + return (payer, receiver) + } + return nil + } } diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift index 19db37c0e..5ada14567 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentView.swift @@ -68,23 +68,9 @@ struct GroupPaymentView: View { .scrollIndicators(.hidden) .scrollBounceBehavior(.basedOnSize) - PrimaryButton(text: "Done", showLoader: viewModel.showLoader, onClick: { - Task { - let isSucceed = await viewModel.handleSaveAction() - if isSucceed { - dismiss() - } else { - viewModel.showSaveFailedError() - } - } - }) - .padding([.horizontal, .bottom], 16) - - AddExpenseFooterView(date: $viewModel.paymentDate, showImagePickerOptions: $viewModel.showImagePickerOptions, - expenseImage: viewModel.paymentImage, expenseImageUrl: viewModel.paymentImageUrl, - handleNoteBtnTap: viewModel.handleNoteBtnTap, - handleExpenseImageTap: viewModel.handlePaymentImageTap, - handleActionSelection: viewModel.handleActionSelection(_:)) + FooterView(date: $viewModel.paymentDate, showImagePickerOptions: $viewModel.showImagePickerOptions, + image: viewModel.paymentImage, imageUrl: viewModel.paymentImageUrl, handleNoteBtnTap: viewModel.handleNoteBtnTap, + handleImageTap: viewModel.handlePaymentImageTap, handleActionSelection: viewModel.handleActionSelection(_:)) } } } @@ -101,6 +87,18 @@ struct GroupPaymentView: View { ToolbarItem(placement: .topBarLeading) { NavigationTitleTextView(text: viewModel.transactionId != nil ? "Edit payment" : "Record a payment") } + ToolbarItem(placement: .topBarTrailing) { + CheckmarkButton(showLoader: viewModel.showLoader) { + Task { + let isSucceed = await viewModel.handleSaveAction() + if isSucceed { + dismiss() + } else { + viewModel.showSaveFailedError() + } + } + } + } } .sheet(isPresented: $viewModel.showImagePicker) { ImagePickerView(cropOption: .square, diff --git a/Splito/UI/Home/Groups/Group/Group Options/Transactions/Transaction Detail/GroupTransactionDetailView.swift b/Splito/UI/Home/Groups/Group/Group Options/Transactions/Transaction Detail/GroupTransactionDetailView.swift index 5a575ce1b..6b20f16a5 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Transactions/Transaction Detail/GroupTransactionDetailView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Transactions/Transaction Detail/GroupTransactionDetailView.swift @@ -51,7 +51,7 @@ struct GroupTransactionDetailView: View { } if let note = viewModel.transaction?.note, !note.isEmpty { - ExpenseNoteView(note: note, handleNoteTap: viewModel.handleNoteTap) + NoteContentView(note: note, handleNoteTap: viewModel.handleNoteTap) .padding(.top, 16) } diff --git a/Splito/UI/Home/Groups/Group/Group Options/Transactions/Transaction Detail/GroupTransactionDetailViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Transactions/Transaction Detail/GroupTransactionDetailViewModel.swift index 470466735..7a7242649 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Transactions/Transaction Detail/GroupTransactionDetailViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Transactions/Transaction Detail/GroupTransactionDetailViewModel.swift @@ -91,6 +91,7 @@ class GroupTransactionDetailViewModel: BaseViewModel, ObservableObject { } self.transactionUsersData = userData + self.paymentNote = transaction.note ?? "" } private func fetchUserData(for userId: String) async -> AppUser? {