Skip to content

Commit

Permalink
Add expense repo and store
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-amisha-i committed Mar 29, 2024
1 parent 90aac1b commit 0af6414
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 79 deletions.
6 changes: 5 additions & 1 deletion Data/Data.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
D85E86DE2BAB0292002EDF76 /* Expense.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85E86DD2BAB0292002EDF76 /* Expense.swift */; };
D85E86E52BAB088F002EDF76 /* ExpenseRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85E86E42BAB088F002EDF76 /* ExpenseRepository.swift */; };
D88721432B99F133009DC5BE /* StorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88721422B99F133009DC5BE /* StorageManager.swift */; };
D8910E382BB6D1D300877CE0 /* ExpenseStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8910E372BB6D1D300877CE0 /* ExpenseStore.swift */; };
D89DBE1D2B872F0B00E5F1BD /* NonceGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89DBE1C2B872F0B00E5F1BD /* NonceGenerator.swift */; };
D89DBE282B88802800E5F1BD /* Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89DBE272B88802800E5F1BD /* Country.swift */; };
D89DBE2B2B88817E00E5F1BD /* JSONUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D89DBE2A2B88817E00E5F1BD /* JSONUtils.swift */; };
Expand Down Expand Up @@ -58,6 +59,7 @@
D85E86DD2BAB0292002EDF76 /* Expense.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expense.swift; sourceTree = "<group>"; };
D85E86E42BAB088F002EDF76 /* ExpenseRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpenseRepository.swift; sourceTree = "<group>"; };
D88721422B99F133009DC5BE /* StorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageManager.swift; sourceTree = "<group>"; };
D8910E372BB6D1D300877CE0 /* ExpenseStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpenseStore.swift; sourceTree = "<group>"; };
D89DBE1C2B872F0B00E5F1BD /* NonceGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGenerator.swift; sourceTree = "<group>"; };
D89DBE272B88802800E5F1BD /* Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Country.swift; sourceTree = "<group>"; };
D89DBE2A2B88817E00E5F1BD /* JSONUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONUtils.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -191,6 +193,7 @@
D8AC25BA2B7F327A00CEAAD3 /* SplitoPreference.swift */,
D8A7CA742BA5AB670014EC67 /* UserStore.swift */,
D8A7CA762BA5AB800014EC67 /* GroupStore.swift */,
D8910E372BB6D1D300877CE0 /* ExpenseStore.swift */,
D8A7CA7A2BA5B6AC0014EC67 /* ShareCodeStore.swift */,
);
path = Store;
Expand Down Expand Up @@ -259,8 +262,8 @@
D89DBE512B8DC4ED00E5F1BD /* Router */,
D8AC25BF2B7F38C800CEAAD3 /* DI */,
D89DBE292B88817200E5F1BD /* Utils */,
D8AC25B92B7F326B00CEAAD3 /* Store */,
D89DBE262B88801F00E5F1BD /* Model */,
D8AC25B92B7F326B00CEAAD3 /* Store */,
D83B15072B99976F004A5F4F /* Repository */,
D8A7CA7E2BA867C80014EC67 /* Extension */,
D89DBE4B2B8CBEB500E5F1BD /* Services */,
Expand Down Expand Up @@ -470,6 +473,7 @@
D89DBE2B2B88817E00E5F1BD /* JSONUtils.swift in Sources */,
D8A7CA802BA867F80014EC67 /* String+Extension.swift in Sources */,
D89DBE282B88802800E5F1BD /* Country.swift in Sources */,
D8910E382BB6D1D300877CE0 /* ExpenseStore.swift in Sources */,
D83B15092B999789004A5F4F /* GroupRepository.swift in Sources */,
D89DBE532B8DC9F700E5F1BD /* AppRoute.swift in Sources */,
D8A7CA752BA5AB670014EC67 /* UserStore.swift in Sources */,
Expand Down
12 changes: 12 additions & 0 deletions Data/Data/DI/AppAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class AppAssembly: Assembly {
StorageManager.init()
}.inObjectScope(.container)

// MARK: - Stores

container.register(UserStore.self) { _ in
UserStore.init()
}.inObjectScope(.container)
Expand All @@ -43,6 +45,12 @@ public class AppAssembly: Assembly {
ShareCodeStore.init()
}.inObjectScope(.container)

container.register(ExpenseStore.self) { _ in
ExpenseStore.init()
}.inObjectScope(.container)

// MARK: - Repositories

container.register(UserRepository.self) { _ in
UserRepository.init()
}.inObjectScope(.container)
Expand All @@ -54,5 +62,9 @@ public class AppAssembly: Assembly {
container.register(ShareCodeRepository.self) { _ in
ShareCodeRepository.init()
}.inObjectScope(.container)

container.register(ExpenseRepository.self) { _ in
ExpenseRepository.init()
}.inObjectScope(.container)
}
}
14 changes: 9 additions & 5 deletions Data/Data/Model/Expense.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,30 @@ public struct Expense: Codable {

let name: String
let amount: Double
let date: Date
let paidBy: AppUser
let splitTo: [String] // Reference to users involved in the split
let date: Timestamp
let paidBy: String
let splitTo: [String] // Reference to user ids involved in the split
let groupId: String
let splitType: SplitType

public init(name: String, amount: Double, date: Date, paidBy: AppUser, splitTo: [String], splitType: SplitType) {
public init(name: String, amount: Double, date: Timestamp, paidBy: String,
splitTo: [String], groupId: String, splitType: SplitType = .equally) {
self.name = name
self.amount = amount
self.date = date
self.paidBy = paidBy
self.splitTo = splitTo
self.groupId = groupId
self.splitType = splitType
}

enum CodingKeys: String, CodingKey {
case name
case amount
case date
case paidBy = "paied_by"
case paidBy = "paid_by"
case splitTo = "split_to"
case groupId = "group_id"
case splitType = "split_type"
}
}
Expand Down
18 changes: 17 additions & 1 deletion Data/Data/Repository/ExpenseRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,20 @@
// Created by Amisha Italiya on 20/03/24.
//

import Foundation
import Combine
import FirebaseFirestoreInternal

public class ExpenseRepository: ObservableObject {

@Inject private var store: ExpenseStore

private var cancelable = Set<AnyCancellable>()

public func addExpense(expense: Expense, completion: @escaping (String?) -> Void) {
store.addExpense(expense: expense, completion: completion)
}

public func deleteExpense(id: String) -> AnyPublisher<Void, ServiceError> {
store.deleteExpense(id: id)
}
}
2 changes: 1 addition & 1 deletion Data/Data/Repository/GroupRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class GroupRepository: ObservableObject {
Future { [weak self] promise in
guard let self else { return }

self.store.fetchGroups(userId: userId)
self.store.fetchGroups()
.sink { completion in
if case .failure(let error) = completion {
promise(.failure(error))
Expand Down
80 changes: 80 additions & 0 deletions Data/Data/Store/ExpenseStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// ExpenseStore.swift
// Data
//
// Created by Amisha Italiya on 29/03/24.
//

import Combine
import FirebaseFirestoreInternal

public class ExpenseStore: ObservableObject {

@Inject private var database: Firestore

private let DATABASE_NAME: String = "expenses"

public func addExpense(expense: Expense, completion: @escaping (String?) -> Void) {
do {
let code = try database.collection(DATABASE_NAME).addDocument(from: expense)
completion(code.documentID)
return
} catch {
LogE("ExpenseRepository :: \(#function) error: \(error.localizedDescription)")
}
completion(nil)
}

public func fetchExpensesBy(groupId: String) -> AnyPublisher<[Expense], ServiceError> {
return Future { [weak self] promise in
guard let self = self else {
promise(.failure(.unexpectedError))
return
}

self.database.collection(DATABASE_NAME).whereField("group_id", isEqualTo: groupId).getDocuments { snapshot, error in
if let error = error {
LogE("ExpenseRepository :: \(#function) error: \(error.localizedDescription)")
promise(.failure(.networkError))
return
}

guard let snapshot, !snapshot.documents.isEmpty else {
LogD("ExpenseRepository :: \(#function) The document is not available.")
promise(.success([]))
return
}

do {
let expenses = try snapshot.documents.compactMap { document in
try document.data(as: Expense.self)
}
promise(.success(expenses))
} catch {
LogE("ExpenseRepository :: \(#function) Decode error: \(error.localizedDescription)")
promise(.failure(.decodingError))
}
}
}
.eraseToAnyPublisher()
}

public func deleteExpense(id: String) -> AnyPublisher<Void, ServiceError> {
Future { [weak self] promise in
guard let self else {
promise(.failure(.unexpectedError))
return
}

self.database.collection(self.DATABASE_NAME).document(id).delete { error in
if let error {
LogE("ExpenseRepository :: \(#function): Deleting collection failed with error: \(error.localizedDescription).")
promise(.failure(.databaseError))
} else {
LogD("ExpenseRepository :: \(#function): expense deleted successfully.")
promise(.success(()))
}
}
}.eraseToAnyPublisher()
}
}
8 changes: 4 additions & 4 deletions Data/Data/Store/GroupStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class GroupStore: ObservableObject {
}.eraseToAnyPublisher()
}

func fetchGroups(userId: String) -> AnyPublisher<[Groups], ServiceError> {
func fetchGroups() -> AnyPublisher<[Groups], ServiceError> {
Future { [weak self] promise in
guard let self else {
promise(.failure(.unexpectedError))
Expand All @@ -56,9 +56,9 @@ class GroupStore: ObservableObject {
return
}

guard let snapshot else {
LogE("GroupStore :: \(#function) The document is not available.")
promise(.failure(.databaseError))
guard let snapshot, !snapshot.documents.isEmpty else {
LogD("GroupStore :: \(#function) The document is not available.")
promise(.success([]))
return
}

Expand Down
2 changes: 1 addition & 1 deletion Data/Data/Store/ShareCodeStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class ShareCodeStore: ObservableObject {
}

public func deleteSharedCode(documentId: String) -> AnyPublisher<Void, ServiceError> {
return Future { [weak self] promise in
Future { [weak self] promise in
guard let self else {
promise(.failure(.unexpectedError))
return
Expand Down
71 changes: 43 additions & 28 deletions Splito/UI/Home/Expense/AddExpenseView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,33 @@ struct AddExpenseView: View {

var body: some View {
VStack(spacing: 25) {
GroupSelectionView(name: viewModel.selectedGroup?.name ?? "Group") {
viewModel.showGroupSelection = true
}
if case .loading = viewModel.currentViewState {
LoaderView(tintColor: primaryColor, scaleSize: 2)
} else {
GroupSelectionView(name: viewModel.selectedGroup?.name ?? "Group") {
viewModel.showGroupSelection = true
}

VStack(spacing: 16) {
ExpenseDetailRow(imageName: "note.text", placeholder: "Enter a description",
text: $viewModel.expenseName, date: $viewModel.expenseDate)
ExpenseDetailRow(imageName: "indianrupeesign.square", placeholder: "0.00",
text: $viewModel.expenseAmount, date: $viewModel.expenseDate, keyboardType: .numberPad)
ExpenseDetailRow(imageName: "calendar", placeholder: "Expense date", forDatePicker: true,
text: .constant(""), date: $viewModel.expenseDate)
}
.padding(.trailing, 20)
VStack(spacing: 16) {
ExpenseDetailRow(imageName: "note.text", placeholder: "Enter a description",
name: $viewModel.expenseName, amount: .constant(0), date: $viewModel.expenseDate)
ExpenseDetailRow(imageName: "indianrupeesign.square", placeholder: "0.00",
name: .constant(""), amount: $viewModel.expenseAmount, date: $viewModel.expenseDate, keyboardType: .numberPad)
ExpenseDetailRow(imageName: "calendar", placeholder: "Expense date", forDatePicker: true,
name: .constant(""), amount: .constant(0), date: $viewModel.expenseDate)
}
.padding(.trailing, 20)

PaidByView(payerName: viewModel.payerName) {
viewModel.showPayerSelection = viewModel.selectedGroup != nil
PaidByView(payerName: viewModel.payerName) {
viewModel.showPayerSelection = viewModel.selectedGroup != nil
}
}
}
.padding(.horizontal, 20)
.background(backgroundColor)
.navigationBarTitle("Add an expense", displayMode: .inline)
.toastView(toast: $viewModel.toast)
.backport.alert(isPresented: $viewModel.showAlert, alertStruct: viewModel.alert)
.sheet(isPresented: $viewModel.showGroupSelection) {
ChooseGroupView(viewModel: ChooseGroupViewModel(selectedGroup: viewModel.selectedGroup) { group in
viewModel.selectedGroup = group
Expand All @@ -50,31 +56,35 @@ struct AddExpenseView: View {
}
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button {
Button("Cancel") {
dismiss()
} label: {
Text("Cancel")
}
}
ToolbarItem(placement: .topBarTrailing) {
Button {
dismiss()
} label: {
Text("Save")
Button("Add") {
viewModel.saveExpense {
dismiss()
}
}
.foregroundColor(primaryColor)
}
}
}
}

struct ExpenseDetailRow: View {
private struct ExpenseDetail: Codable {
var name: String
var amount: Double
}

private struct ExpenseDetailRow: View {

var imageName: String
var placeholder: String
var forDatePicker: Bool = false

@Binding var text: String
@Binding var name: String
@Binding var amount: Double
@Binding var date: Date

var keyboardType: UIKeyboardType = .default
Expand All @@ -96,9 +106,14 @@ struct ExpenseDetailRow: View {
.font(.subTitle2())
} else {
VStack {
TextField(placeholder, text: $text)
.font(.subTitle2())
.keyboardType(keyboardType)
if keyboardType == .default {
TextField(placeholder, text: $name)
.font(.subTitle2())
} else {
TextField("Amount", value: $amount, formatter: NumberFormatter())
.font(.subTitle2())
.keyboardType(keyboardType)
}

Divider()
.background(Color.gray)
Expand All @@ -109,7 +124,7 @@ struct ExpenseDetailRow: View {
}
}

struct GroupSelectionView: View {
private struct GroupSelectionView: View {

var name: String
var onTap: () -> Void
Expand Down Expand Up @@ -138,7 +153,7 @@ struct GroupSelectionView: View {
}
}

struct PaidByView: View {
private struct PaidByView: View {

let payerName: String
var onTap: () -> Void
Expand Down
Loading

0 comments on commit 0af6414

Please sign in to comment.