From a11e6e0d5490a90c7842278b8208c8018141b86b Mon Sep 17 00:00:00 2001 From: Amisha Canopas Date: Thu, 5 Dec 2024 15:00:57 +0530 Subject: [PATCH] Fix UI issues --- Data/Data/Model/Groups.swift | 5 ++- Data/Data/Repository/GroupRepository.swift | 43 +++++++++++++------ Data/Data/Store/ExpenseStore.swift | 2 +- Data/Data/Store/GroupStore.swift | 2 +- Data/Data/Store/TransactionStore.swift | 2 +- Data/Data/Store/UserStore.swift | 2 +- Splito/Localization/Localizable.xcstrings | 13 ++++-- .../UI/Home/Expense/AddExpenseViewModel.swift | 7 +-- .../Payer/ChooseMultiplePayerView.swift | 2 +- .../ExpenseSplitOptionsView.swift | 2 +- .../Add Member/InviteMemberViewModel.swift | 3 +- .../Create Group/CreateGroupViewModel.swift | 5 ++- .../Balances/GroupBalancesView.swift | 10 ++++- .../Balances/GroupBalancesViewModel.swift | 4 +- .../Settle up/GroupSettleUpViewModel.swift | 13 ++---- .../Payment/GroupPaymentViewModel.swift | 10 ++--- .../Who Is paying/GroupWhoIsPayingView.swift | 12 +++++- .../Totals/GroupTotalsViewModel.swift | 3 +- .../GroupTransactionListView.swift | 7 ++- .../GroupTransactionListViewModel.swift | 2 +- .../GroupTransactionDetailViewModel.swift | 9 ++-- .../Group Setting/GroupSettingViewModel.swift | 3 +- .../Groups/Group/GroupExpenseListView.swift | 7 ++- .../Groups/Group/GroupHomeViewModel.swift | 2 +- .../Group/GroupHomeViewModelExtension.swift | 2 +- Splito/UI/Home/Groups/GroupListView.swift | 1 + .../UI/Home/Groups/GroupListViewModel.swift | 18 +++++++- 27 files changed, 122 insertions(+), 69 deletions(-) diff --git a/Data/Data/Model/Groups.swift b/Data/Data/Model/Groups.swift index 273cb9654..97e6ada87 100644 --- a/Data/Data/Model/Groups.swift +++ b/Data/Data/Model/Groups.swift @@ -22,8 +22,9 @@ public struct Groups: Codable, Identifiable { public var hasExpenses: Bool public var isActive: Bool - public init(name: String, createdBy: String, updatedBy: String, imageUrl: String? = nil, members: [String], balances: [GroupMemberBalance], - createdAt: Timestamp, updatedAt: Timestamp, hasExpenses: Bool = false, isActive: Bool = true) { + public init(name: String, createdBy: String, updatedBy: String, imageUrl: String? = nil, + members: [String], balances: [GroupMemberBalance], createdAt: Timestamp = Timestamp(), + updatedAt: Timestamp = Timestamp(), hasExpenses: Bool = false, isActive: Bool = true) { self.name = name self.createdBy = createdBy self.updatedBy = updatedBy diff --git a/Data/Data/Repository/GroupRepository.swift b/Data/Data/Repository/GroupRepository.swift index 42bce377e..4702bd011 100644 --- a/Data/Data/Repository/GroupRepository.swift +++ b/Data/Data/Repository/GroupRepository.swift @@ -114,10 +114,12 @@ public class GroupRepository: ObservableObject { } public func deleteGroup(group: Groups) async throws { - var group = group + guard let userId = preference.user?.id else { return } - // Make group inactive - group.isActive = false + var group = group + group.isActive = false // Make group inactive + group.updatedBy = userId + group.updatedAt = Timestamp() try await updateGroup(group: group, type: .groupDeleted) } @@ -193,17 +195,19 @@ public class GroupRepository: ObservableObject { try await store.fetchGroupsBy(userId: userId, limit: limit, lastDocument: lastDocument) } - public func fetchMemberBy(userId: String) async throws -> AppUser? { + public func fetchMemberBy(memberId: String) async throws -> AppUser? { // Use a synchronous read to check if the member already exists in groupMembers - let existingMember = groupMembersQueue.sync { groupMembers.first(where: { $0.id == userId }) } - - if let existingMember { - return existingMember // Return the available member from groupMembers + if let existingMember = groupMembersQueue.sync(execute: { + groupMembers.first(where: { $0.id == memberId }) + }) { + await updateCurrentUserImageUrl(for: [memberId]) + return existingMember // Return the cached member } - let member = try await userRepository.fetchUserBy(userID: userId) + // Fetch the member from the repository if not found locally + let member = try await userRepository.fetchUserBy(userID: memberId) - // Append to groupMembers safely with a barrier, ensuring thread safety + // Append the newly fetched member to groupMembers in a thread-safe manner if let member { try await withCheckedThrowingContinuation { continuation in groupMembersQueue.async(flags: .barrier) { @@ -218,23 +222,26 @@ public class GroupRepository: ObservableObject { public func fetchMembersBy(memberIds: [String]) async throws -> [AppUser] { var members: [AppUser] = [] - // Filter out memberIds that already exist in groupMembers to minimise API calls + // Filter out memberIds that already exist in groupMembers to minimize API calls let missingMemberIds = memberIds.filter { memberId in let cachedMember = self.groupMembersQueue.sync { self.groupMembers.first { $0.id == memberId } } return cachedMember == nil } if missingMemberIds.isEmpty { + await updateCurrentUserImageUrl(for: memberIds) return groupMembersQueue.sync { self.groupMembers.filter { memberIds.contains($0.id) } } } + // Fetch missing members concurrently using a TaskGroup try await withThrowingTaskGroup(of: AppUser?.self) { groupTask in for memberId in missingMemberIds { groupTask.addTask { - try await self.fetchMemberBy(userId: memberId) + try await self.fetchMemberBy(memberId: memberId) } } + // Collect results from the task group & add to the groupMembers array for try await member in groupTask { if let member { members.append(member) @@ -249,4 +256,16 @@ public class GroupRepository: ObservableObject { return members } + + // Updates the current user's image url in groupMembers if applicable + private func updateCurrentUserImageUrl(for memberIds: [String]) async { + guard let currentUser = preference.user, memberIds.contains(currentUser.id) else { return } + + groupMembersQueue.async(flags: .barrier) { + if let index = self.groupMembers.firstIndex(where: { $0.id == currentUser.id }), + self.groupMembers[index].imageUrl != currentUser.imageUrl { + self.groupMembers[index].imageUrl = currentUser.imageUrl + } + } + } } diff --git a/Data/Data/Store/ExpenseStore.swift b/Data/Data/Store/ExpenseStore.swift index 236879ce5..1e2d4c2e0 100644 --- a/Data/Data/Store/ExpenseStore.swift +++ b/Data/Data/Store/ExpenseStore.swift @@ -31,7 +31,7 @@ public class ExpenseStore: ObservableObject { func updateExpense(groupId: String, expense: Expense) async throws { if let expenseId = expense.id { - try expenseReference(groupId: groupId).document(expenseId).setData(from: expense, merge: true) + try expenseReference(groupId: groupId).document(expenseId).setData(from: expense, merge: false) } else { LogE("ExpenseStore: \(#function) Expense not found.") throw ServiceError.dataNotFound diff --git a/Data/Data/Store/GroupStore.swift b/Data/Data/Store/GroupStore.swift index df418e92c..899d6755f 100644 --- a/Data/Data/Store/GroupStore.swift +++ b/Data/Data/Store/GroupStore.swift @@ -35,7 +35,7 @@ class GroupStore: ObservableObject { func updateGroup(group: Groups) async throws { if let groupId = group.id { - try groupReference.document(groupId).setData(from: group, merge: true) + try groupReference.document(groupId).setData(from: group, merge: false) } else { LogE("GroupStore: \(#function) Group not found.") throw ServiceError.dataNotFound diff --git a/Data/Data/Store/TransactionStore.swift b/Data/Data/Store/TransactionStore.swift index ece45943f..2999743ef 100644 --- a/Data/Data/Store/TransactionStore.swift +++ b/Data/Data/Store/TransactionStore.swift @@ -31,7 +31,7 @@ public class TransactionStore: ObservableObject { func updateTransaction(groupId: String, transaction: Transactions) async throws { if let transactionId = transaction.id { - try transactionReference(groupId: groupId).document(transactionId).setData(from: transaction, merge: true) + try transactionReference(groupId: groupId).document(transactionId).setData(from: transaction, merge: false) } else { LogE("TransactionStore: \(#function) Payment not found.") throw ServiceError.dataNotFound diff --git a/Data/Data/Store/UserStore.swift b/Data/Data/Store/UserStore.swift index aa4a358c2..1102ec85b 100644 --- a/Data/Data/Store/UserStore.swift +++ b/Data/Data/Store/UserStore.swift @@ -28,7 +28,7 @@ class UserStore: ObservableObject { } func updateUser(user: AppUser) async throws -> AppUser? { - try usersCollection.document(user.id).setData(from: user, merge: true) + try usersCollection.document(user.id).setData(from: user, merge: false) return user } diff --git a/Splito/Localization/Localizable.xcstrings b/Splito/Localization/Localizable.xcstrings index 329ded790..948ac5d07 100644 --- a/Splito/Localization/Localizable.xcstrings +++ b/Splito/Localization/Localizable.xcstrings @@ -45,6 +45,16 @@ }, "%@" : { + }, + "%@ %@ " : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ %2$@ " + } + } + } }, "%@ %@ %@" : { "localizations" : { @@ -61,9 +71,6 @@ }, "%@ and %@" : { "extractionState" : "manual" - }, - "%@ owes " : { - }, "%@ owes you " : { diff --git a/Splito/UI/Home/Expense/AddExpenseViewModel.swift b/Splito/UI/Home/Expense/AddExpenseViewModel.swift index 09e2cd02c..2986402ed 100644 --- a/Splito/UI/Home/Expense/AddExpenseViewModel.swift +++ b/Splito/UI/Home/Expense/AddExpenseViewModel.swift @@ -163,7 +163,7 @@ class AddExpenseViewModel: BaseViewModel, ObservableObject { private func fetchUserData(for userId: String) async -> AppUser? { do { - let user = try await groupRepository.fetchMemberBy(userId: userId) + let user = try await groupRepository.fetchMemberBy(memberId: userId) LogD("AddExpenseViewModel: \(#function) Member fetched successfully.") return user } catch { @@ -389,7 +389,7 @@ extension AddExpenseViewModel { if !group.hasExpenses { selectedGroup?.hasExpenses = true } - await updateGroupMemberBalance(expense: expense, updateType: .Add) + await updateGroupMemberBalance(expense: newExpense, updateType: .Add) showLoader = false LogD("AddExpenseViewModel: \(#function) Expense added successfully.") @@ -458,7 +458,8 @@ extension AddExpenseViewModel { private func hasExpenseChanged(_ expense: Expense, oldExpense: Expense) -> Bool { return oldExpense.amount != expense.amount || oldExpense.paidBy != expense.paidBy || oldExpense.splitTo != expense.splitTo || oldExpense.splitType != expense.splitType || - oldExpense.splitData != expense.splitData || oldExpense.isActive != expense.isActive + oldExpense.splitData != expense.splitData || oldExpense.isActive != expense.isActive || + oldExpense.date != expense.date } private func updateGroupMemberBalance(expense: Expense, updateType: ExpenseUpdateType) async { diff --git a/Splito/UI/Home/Expense/Detail Selection/Payer/ChooseMultiplePayerView.swift b/Splito/UI/Home/Expense/Detail Selection/Payer/ChooseMultiplePayerView.swift index d91132d39..a918cf3f2 100644 --- a/Splito/UI/Home/Expense/Detail Selection/Payer/ChooseMultiplePayerView.swift +++ b/Splito/UI/Home/Expense/Detail Selection/Payer/ChooseMultiplePayerView.swift @@ -45,7 +45,7 @@ struct ChooseMultiplePayerView: View { .scrollIndicators(.hidden) .scrollBounceBehavior(.basedOnSize) - BottomInfoCardView(title: "₹ \(String(format: "%.2f", viewModel.totalAmount)) of \(viewModel.expenseAmount.formattedCurrency)", + BottomInfoCardView(title: "\(viewModel.totalAmount.formattedCurrency) of \(viewModel.expenseAmount.formattedCurrency)", value: "\((viewModel.expenseAmount - viewModel.totalAmount).formattedCurrencyWithSign) left") } } diff --git a/Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsView.swift b/Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsView.swift index 3f196b76d..5e9b91916 100644 --- a/Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsView.swift +++ b/Splito/UI/Home/Expense/Expense Split Option/ExpenseSplitOptionsView.swift @@ -75,7 +75,7 @@ private struct SplitOptionsBottomView: View { memberCount: viewModel.selectedMembers.count, isAllSelected: viewModel.isAllSelected, isForEqualSplit: true, onAllBtnTap: viewModel.handleAllBtnAction) case .fixedAmount: - BottomInfoCardView(title: "₹ \(String(format: "%.2f", viewModel.totalFixedAmount)) of \(viewModel.expenseAmount.formattedCurrency)", + BottomInfoCardView(title: "\(viewModel.totalFixedAmount.formattedCurrency) of \(viewModel.expenseAmount.formattedCurrency)", value: "\((viewModel.expenseAmount - viewModel.totalFixedAmount).formattedCurrencyWithSign) left") case .percentage: BottomInfoCardView(title: "\(String(format: "%.0f", viewModel.totalPercentage))% of 100%", diff --git a/Splito/UI/Home/Groups/Add Member/InviteMemberViewModel.swift b/Splito/UI/Home/Groups/Add Member/InviteMemberViewModel.swift index 83b0a0444..74ba8e38c 100644 --- a/Splito/UI/Home/Groups/Add Member/InviteMemberViewModel.swift +++ b/Splito/UI/Home/Groups/Add Member/InviteMemberViewModel.swift @@ -41,8 +41,7 @@ class InviteMemberViewModel: BaseViewModel, ObservableObject { // MARK: - Data Loading private func fetchGroup() async { do { - let group = try await groupRepository.fetchGroupBy(id: groupId) - self.group = group + self.group = try await groupRepository.fetchGroupBy(id: groupId) viewState = .initial LogD("InviteMemberViewModel: \(#function) Group fetched successfully.") } catch { diff --git a/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift b/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift index 3baa59742..a822abade 100644 --- a/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift +++ b/Splito/UI/Home/Groups/Create Group/CreateGroupViewModel.swift @@ -38,6 +38,7 @@ class CreateGroupViewModel: BaseViewModel, ObservableObject { super.init() } +// MARK: - User Actions private func checkCameraPermission(authorized: @escaping (() -> Void)) { switch AVCaptureDevice.authorizationStatus(for: .video) { case .notDetermined: @@ -100,8 +101,8 @@ class CreateGroupViewModel: BaseViewModel, ObservableObject { guard let userId = preference.user?.id else { return false } let memberBalance = GroupMemberBalance(id: userId, balance: 0, totalSummary: []) - let group = Groups(name: groupName.trimming(spaces: .leadingAndTrailing), createdBy: userId, updatedBy: userId, imageUrl: nil, - members: [userId], balances: [memberBalance], createdAt: Timestamp(), updatedAt: Timestamp()) + let group = Groups(name: groupName.trimming(spaces: .leadingAndTrailing), createdBy: userId, + updatedBy: userId, members: [userId], balances: [memberBalance]) do { showLoader = true diff --git a/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesView.swift b/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesView.swift index d9c6ffca0..7b83f73ed 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesView.swift @@ -7,6 +7,7 @@ import SwiftUI import BaseStyle +import Data struct GroupBalancesView: View { @@ -71,6 +72,8 @@ struct GroupBalancesView: View { @MainActor private struct GroupBalanceItemView: View { + @Inject private var preference: SplitoPreference + let memberBalance: MembersCombinedBalance let viewModel: GroupBalancesViewModel @@ -88,7 +91,7 @@ private struct GroupBalanceItemView: View { let hasDue = memberBalance.totalOwedAmount < 0 let name = viewModel.getMemberName(id: memberBalance.id, needFullName: true) - let owesOrGetsBack = hasDue ? "owes" : "gets back" + let owesOrGetsBack = hasDue ? (memberBalance.id == preference.user?.id ? "owe" : "owes") : (memberBalance.id == preference.user?.id ? "get back" : "gets back") if memberBalance.totalOwedAmount == 0 { Group { @@ -142,6 +145,8 @@ private struct GroupBalanceItemView: View { private struct GroupBalanceItemMemberView: View { let SUB_IMAGE_HEIGHT: CGFloat = 24 + @Inject private var preference: SplitoPreference + let id: String let balances: [String: Double] let viewModel: GroupBalancesViewModel @@ -156,13 +161,14 @@ private struct GroupBalanceItemMemberView: View { let imageUrl = viewModel.getMemberImage(id: memberId) let owesMemberName = viewModel.getMemberName(id: hasDue ? memberId : id) let owedMemberName = viewModel.getMemberName(id: hasDue ? id : memberId) + let owesText = ((hasDue ? id : memberId) == preference.user?.id) ? "owe" : "owes" VStack(alignment: .leading, spacing: 8) { HStack(alignment: .center, spacing: 16) { MemberProfileImageView(imageUrl: imageUrl, height: SUB_IMAGE_HEIGHT, scaleEffect: 0.6) Group { - Text("\(owedMemberName) owes ") + Text("\(owedMemberName.capitalized) \(owesText.localized) ") + Text(amount.formattedCurrency) .foregroundColor(hasDue ? errorColor : successColor) diff --git a/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesViewModel.swift index 08c352afd..ec9875462 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Balances/GroupBalancesViewModel.swift @@ -130,8 +130,8 @@ class GroupBalancesViewModel: BaseViewModel, ObservableObject { } func getMemberName(id: String, needFullName: Bool = false) -> String { - guard let member = getMemberDataBy(id: id) else { return "" } - return needFullName ? member.fullName : member.nameWithLastInitial + guard let userId = preference.user?.id, let member = getMemberDataBy(id: id) else { return "" } + return needFullName ? (id == userId ? "You" : member.fullName) : (id == userId ? "you" : member.nameWithLastInitial) } // MARK: - User Actions diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpViewModel.swift index a268b074f..2d7ac721b 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/GroupSettleUpViewModel.swift @@ -39,13 +39,8 @@ class GroupSettleUpViewModel: BaseViewModel, ObservableObject { // MARK: - Data Loading func fetchGroupDetails() async { do { - let group = try await groupRepository.fetchGroupBy(id: groupId) - guard let group else { - viewState = .initial - return - } - self.group = group - calculateMemberPayableAmount(group: group) + self.group = try await groupRepository.fetchGroupBy(id: groupId) + calculateMemberPayableAmount() viewState = .initial LogD("GroupSettleUpViewModel: \(#function) Group fetched successfully.") } catch { @@ -54,8 +49,8 @@ class GroupSettleUpViewModel: BaseViewModel, ObservableObject { } } - func calculateMemberPayableAmount(group: Groups) { - guard let userId = preference.user?.id else { + func calculateMemberPayableAmount() { + guard let group, let userId = preference.user?.id else { viewState = .initial return } diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift index 295258f30..6f9d473ac 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Payment/GroupPaymentViewModel.swift @@ -124,8 +124,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { private func getPayerUserDetail() async { do { viewState = .loading - let user = try await userRepository.fetchUserBy(userID: payerId) - if let user { payer = user } + payer = try await userRepository.fetchUserBy(userID: payerId) viewState = .initial LogD("GroupPaymentViewModel: \(#function) Payer fetched successfully.") } catch { @@ -137,8 +136,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { private func getPayableUserDetail() async { do { viewState = .loading - let user = try await userRepository.fetchUserBy(userID: receiverId) - if let user { receiver = user } + receiver = try await userRepository.fetchUserBy(userID: receiverId) viewState = .initial LogD("GroupPaymentViewModel: \(#function) Payable fetched successfully.") } catch { @@ -294,6 +292,7 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { NotificationCenter.default.post(name: .updateTransaction, object: self.transaction) } + guard let transaction = self.transaction else { return false } guard hasTransactionChanged(transaction, oldTransaction: oldTransaction) else { return true } await updateGroupMemberBalance(updateType: .Update(oldTransaction: oldTransaction)) @@ -316,7 +315,8 @@ class GroupPaymentViewModel: BaseViewModel, ObservableObject { private func hasTransactionChanged(_ transaction: Transactions, oldTransaction: Transactions) -> Bool { return oldTransaction.payerId != transaction.payerId || oldTransaction.receiverId != transaction.receiverId || - oldTransaction.amount != transaction.amount || oldTransaction.isActive != transaction.isActive + oldTransaction.amount != transaction.amount || oldTransaction.isActive != transaction.isActive || + oldTransaction.date != transaction.date } private func updateGroupMemberBalance(updateType: TransactionUpdateType) async { diff --git a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Who Is paying/GroupWhoIsPayingView.swift b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Who Is paying/GroupWhoIsPayingView.swift index 466c332c5..86fd45939 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Settle up/Who Is paying/GroupWhoIsPayingView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Settle up/Who Is paying/GroupWhoIsPayingView.swift @@ -53,6 +53,8 @@ struct GroupWhoIsPayingView: View { struct GroupPayingMemberView: View { + @Inject private var preference: SplitoPreference + let member: AppUser let isSelected: Bool @@ -61,6 +63,14 @@ struct GroupPayingMemberView: View { let onMemberTap: (String) -> Void + private var memberName: String { + if let user = preference.user, user.id == member.id { + return "You" + } else { + return member.fullName + } + } + init(member: AppUser, isSelected: Bool = false, isLastMember: Bool, disableMemberTap: Bool = false, onMemberTap: @escaping (String) -> Void) { self.member = member self.isSelected = isSelected @@ -73,7 +83,7 @@ struct GroupPayingMemberView: View { HStack(alignment: .center, spacing: 16) { MemberProfileImageView(imageUrl: member.imageUrl) - Text(member.fullName.localized) + Text(memberName.localized) .font(.subTitle2()) .foregroundStyle(primaryText) .lineLimit(1) diff --git a/Splito/UI/Home/Groups/Group/Group Options/Totals/GroupTotalsViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Totals/GroupTotalsViewModel.swift index 5b7f1a5c5..23376fb6d 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Totals/GroupTotalsViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Totals/GroupTotalsViewModel.swift @@ -35,8 +35,7 @@ class GroupTotalsViewModel: BaseViewModel, ObservableObject { // MARK: - Data Loading private func fetchGroup() async { do { - let latestGroup = try await groupRepository.fetchGroupBy(id: groupId) - group = latestGroup + group = try await groupRepository.fetchGroupBy(id: groupId) filterDataForSelectedTab() viewState = .initial LogD("GroupTotalsViewModel: \(#function) Group fetched successfully.") diff --git a/Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListView.swift b/Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListView.swift index 1152f4c8e..52e3aaa3f 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListView.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListView.swift @@ -57,14 +57,12 @@ private struct TransactionListWithDetailView: View { EmptyTransactionView(geometry: geometry) } else { let firstMonth = viewModel.filteredTransactions.keys.sorted(by: sortMonthYearStrings).first - let lastMonth = viewModel.filteredTransactions.keys.sorted(by: sortMonthYearStrings).last ForEach(viewModel.filteredTransactions.keys.sorted(by: sortMonthYearStrings), id: \.self) { month in Section(header: sectionHeader(month: month)) { ForEach(viewModel.filteredTransactions[month] ?? [], id: \.transaction.id) { transaction in TransactionItemView(transactionWithUser: transaction, isLastCell: transaction.transaction.id == (viewModel.filteredTransactions[month] ?? []).last?.transaction.id) - .padding(.bottom, (month == lastMonth && viewModel.filteredTransactions[month]?.last?.transaction.id == transaction.transaction.id) ? 50 : 0) .onTouchGesture { viewModel.handleTransactionItemTap(transaction.transaction.id) } @@ -129,7 +127,7 @@ private struct TransactionListWithDetailView: View { return Text(month) .font(.Header4()) .foregroundStyle(primaryText) - .padding(.vertical, 8) + .padding(.vertical, 5) .padding(.horizontal, 16) } } @@ -154,7 +152,7 @@ private struct TransactionItemView: View { } var body: some View { - VStack(spacing: 20) { + VStack(spacing: 0) { HStack(alignment: .center, spacing: 0) { let dateComponents = transactionWithUser.transaction.date.dateValue().dayAndMonthText VStack(spacing: 0) { @@ -195,6 +193,7 @@ private struct TransactionItemView: View { } } .padding(.top, 20) + .padding(.bottom, isLastCell ? 13 : 20) .padding(.horizontal, 16) if !isLastCell { diff --git a/Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListViewModel.swift b/Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListViewModel.swift index 458071012..742994115 100644 --- a/Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Options/Transactions/GroupTransactionListViewModel.swift @@ -130,7 +130,7 @@ class GroupTransactionListViewModel: BaseViewModel, ObservableObject { return existingUser // Return the available user from groupMembers } else { do { - let user = try await groupRepository.fetchMemberBy(userId: userId) + let user = try await groupRepository.fetchMemberBy(memberId: userId) if let user { groupMembers.append(user) } 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 848cec7a4..2b921b061 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 @@ -52,8 +52,7 @@ class GroupTransactionDetailViewModel: BaseViewModel, ObservableObject { // MARK: - Data Loading private func fetchGroup() async { do { - let group = try await groupRepository.fetchGroupBy(id: groupId) - self.group = group + self.group = try await groupRepository.fetchGroupBy(id: groupId) viewState = .initial LogD("GroupTransactionDetailViewModel: \(#function) Group fetched successfully.") } catch { @@ -76,7 +75,10 @@ class GroupTransactionDetailViewModel: BaseViewModel, ObservableObject { } private func setTransactionUsersData() async { - guard let transaction else { return } + guard let transaction else { + viewState = .initial + return + } var userData: [AppUser] = [] var members: [String] = [] @@ -201,7 +203,6 @@ class GroupTransactionDetailViewModel: BaseViewModel, ObservableObject { payer: payer, receiver: receiver) NotificationCenter.default.post(name: .deleteTransaction, object: self.transaction) await updateGroupMemberBalance(updateType: .Delete) - self.showToastFor(toast: .init(type: .success, title: "Success", message: "Payment deleted successfully.")) viewState = .initial LogD("GroupTransactionDetailViewModel: \(#function) Payment deleted successfully.") diff --git a/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingViewModel.swift b/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingViewModel.swift index dcf8eaf08..260fd62c9 100644 --- a/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingViewModel.swift +++ b/Splito/UI/Home/Groups/Group/Group Setting/GroupSettingViewModel.swift @@ -45,8 +45,7 @@ class GroupSettingViewModel: BaseViewModel, ObservableObject { // MARK: - Data Loading private func fetchGroupDetails() async { do { - let group = try await groupRepository.fetchGroupBy(id: groupId) - self.group = group + self.group = try await groupRepository.fetchGroupBy(id: groupId) self.checkForGroupAdmin() await fetchGroupMembers() currentViewState = .initial diff --git a/Splito/UI/Home/Groups/Group/GroupExpenseListView.swift b/Splito/UI/Home/Groups/Group/GroupExpenseListView.swift index 611cc6e67..99067f5a3 100644 --- a/Splito/UI/Home/Groups/Group/GroupExpenseListView.swift +++ b/Splito/UI/Home/Groups/Group/GroupExpenseListView.swift @@ -53,14 +53,12 @@ struct GroupExpenseListView: View { onClick: viewModel.openAddExpenseSheet) } else if !viewModel.groupExpenses.isEmpty { let firstMonth = viewModel.groupExpenses.keys.sorted(by: sortMonthYearStrings).first - let lastMonth = viewModel.groupExpenses.keys.sorted(by: sortMonthYearStrings).last ForEach(viewModel.groupExpenses.keys.sorted(by: sortMonthYearStrings), id: \.self) { month in Section(header: sectionHeader(month: month)) { ForEach(viewModel.groupExpenses[month] ?? [], id: \.expense.id) { expense in GroupExpenseItemView(expenseWithUser: expense, isLastItem: expense.expense == (viewModel.groupExpenses[month] ?? []).last?.expense) - .padding(.bottom, (month == lastMonth && viewModel.groupExpenses[month]?.last?.expense.id == expense.expense.id) ? 50 : 0) .onTouchGesture { viewModel.handleExpenseItemTap(expenseId: expense.expense.id ?? "") } @@ -127,7 +125,7 @@ struct GroupExpenseListView: View { .font(.Header4()) .foregroundStyle(primaryText) .padding(.horizontal, 16) - .padding(.vertical, 8) + .padding(.vertical, 5) Spacer() } .onTapGestureForced { @@ -175,7 +173,7 @@ private struct GroupExpenseItemView: View { } var body: some View { - VStack(spacing: 20) { + VStack(spacing: 0) { HStack(alignment: .center, spacing: 0) { let dateComponents = expense.date.dateValue().dayAndMonthText VStack(spacing: 0) { @@ -243,6 +241,7 @@ private struct GroupExpenseItemView: View { } .padding(.horizontal, 16) .padding(.top, 20) + .padding(.bottom, isLastItem ? 13 : 20) if !isLastItem { Divider() diff --git a/Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift b/Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift index 1052e25ab..df7246892 100644 --- a/Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift +++ b/Splito/UI/Home/Groups/Group/GroupHomeViewModel.swift @@ -194,7 +194,7 @@ class GroupHomeViewModel: BaseViewModel, ObservableObject { func fetchMemberData(for memberId: String) async -> AppUser? { do { - let member = try await groupRepository.fetchMemberBy(userId: memberId) + let member = try await groupRepository.fetchMemberBy(memberId: memberId) if let member { addMemberIfNotExist(member) } diff --git a/Splito/UI/Home/Groups/Group/GroupHomeViewModelExtension.swift b/Splito/UI/Home/Groups/Group/GroupHomeViewModelExtension.swift index bbb80435d..8cca02216 100644 --- a/Splito/UI/Home/Groups/Group/GroupHomeViewModelExtension.swift +++ b/Splito/UI/Home/Groups/Group/GroupHomeViewModelExtension.swift @@ -137,7 +137,7 @@ extension GroupHomeViewModel { await updateGroupMemberBalance(expense: deletedExpense, updateType: .Delete) LogD("GroupHomeViewModel: \(#function) Expense deleted successfully.") } catch { - LogE("GroupHomeViewModel: \(#function) Failed to delete expense \(expenseId): \(error)") + LogE("GroupHomeViewModel: \(#function) Failed to delete expense \(expenseId): \(error).") showToastForError() } } diff --git a/Splito/UI/Home/Groups/GroupListView.swift b/Splito/UI/Home/Groups/GroupListView.swift index 81d1282c2..9898de6c2 100644 --- a/Splito/UI/Home/Groups/GroupListView.swift +++ b/Splito/UI/Home/Groups/GroupListView.swift @@ -127,6 +127,7 @@ struct GroupListView: View { JoinMemberView(viewModel: JoinMemberViewModel(router: viewModel.router)) } } + .onAppear(perform: viewModel.fetchCurrentUser) } } diff --git a/Splito/UI/Home/Groups/GroupListViewModel.swift b/Splito/UI/Home/Groups/GroupListViewModel.swift index b47cf18df..9d7f53b68 100644 --- a/Splito/UI/Home/Groups/GroupListViewModel.swift +++ b/Splito/UI/Home/Groups/GroupListViewModel.swift @@ -165,7 +165,7 @@ class GroupListViewModel: BaseViewModel, ObservableObject { if let user { self?.totalOweAmount = user.totalOweAmount } else { - self?.handleServiceError() + self?.showToastForError() } } } @@ -181,6 +181,22 @@ class GroupListViewModel: BaseViewModel, ObservableObject { return nil } } + + func fetchCurrentUser() { + guard let userId = preference.user?.id else { return } + + Task.detached { [weak self] in + do { + guard let self else { return } + let user = try await self.userRepository.fetchUserBy(userID: userId) + await MainActor.run { + self.preference.user = user + } + } catch { + LogE("GroupListViewModel: \(#function) Failed to fetch current user: \(error).") + } + } + } } // MARK: - User Actions