Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monthly recurring expenses and income #30

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
Binary file modified .DS_Store
Binary file not shown.
36 changes: 36 additions & 0 deletions Expenso.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
objects = {

/* Begin PBXBuildFile section */
243DEC7A2848FCC1001281AD /* MonthlyTransactionDetailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 243DEC792848FCC1001281AD /* MonthlyTransactionDetailedView.swift */; };
243DEC7C2848FF55001281AD /* MonthlyTransactionDetailedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 243DEC7B2848FF55001281AD /* MonthlyTransactionDetailedViewModel.swift */; };
2463680C284A35CE007FC25F /* EditMonthlyTransactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2463680B284A35CE007FC25F /* EditMonthlyTransactionView.swift */; };
24AE70432847F81100B2BD49 /* MonthlyTransactionSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24AE70422847F81100B2BD49 /* MonthlyTransactionSettingsView.swift */; };
24DF38B62846B7BC006CA05F /* MonthlyTransactionCD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24DF38B52846B7BC006CA05F /* MonthlyTransactionCD.swift */; };
736C720A25CFD89900720DEA /* empty-face.json in Resources */ = {isa = PBXBuildFile; fileRef = 736C720925CFD89900720DEA /* empty-face.json */; };
736C721B25CFE8E200720DEA /* LottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 736C721A25CFE8E200720DEA /* LottieView.swift */; };
738B1C1825C65DFE0067407B /* ExpensoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B1C1725C65DFE0067407B /* ExpensoApp.swift */; };
Expand Down Expand Up @@ -53,6 +58,11 @@

/* Begin PBXFileReference section */
1B0E373FB89DD0864398FEE7 /* Pods_Expenso.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Expenso.framework; sourceTree = BUILT_PRODUCTS_DIR; };
243DEC792848FCC1001281AD /* MonthlyTransactionDetailedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthlyTransactionDetailedView.swift; sourceTree = "<group>"; };
243DEC7B2848FF55001281AD /* MonthlyTransactionDetailedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthlyTransactionDetailedViewModel.swift; sourceTree = "<group>"; };
2463680B284A35CE007FC25F /* EditMonthlyTransactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMonthlyTransactionView.swift; sourceTree = "<group>"; };
24AE70422847F81100B2BD49 /* MonthlyTransactionSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthlyTransactionSettingsView.swift; sourceTree = "<group>"; };
24DF38B52846B7BC006CA05F /* MonthlyTransactionCD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthlyTransactionCD.swift; sourceTree = "<group>"; };
2C2D1600DAC62FA04EDCF170 /* Pods-Expenso.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Expenso.debug.xcconfig"; path = "Target Support Files/Pods-Expenso/Pods-Expenso.debug.xcconfig"; sourceTree = "<group>"; };
736C720925CFD89900720DEA /* empty-face.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "empty-face.json"; sourceTree = "<group>"; };
736C721A25CFE8E200720DEA /* LottieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -112,9 +122,29 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
243DEC762848FB45001281AD /* MonthlyTransactionSettings */ = {
isa = PBXGroup;
children = (
24AE70422847F81100B2BD49 /* MonthlyTransactionSettingsView.swift */,
);
path = MonthlyTransactionSettings;
sourceTree = "<group>";
};
243DEC782848FCAB001281AD /* MonthlyTransactionDetailed */ = {
isa = PBXGroup;
children = (
243DEC792848FCC1001281AD /* MonthlyTransactionDetailedView.swift */,
2463680B284A35CE007FC25F /* EditMonthlyTransactionView.swift */,
243DEC7B2848FF55001281AD /* MonthlyTransactionDetailedViewModel.swift */,
);
path = MonthlyTransactionDetailed;
sourceTree = "<group>";
};
733763A827120DB600AA983A /* Screens */ = {
isa = PBXGroup;
children = (
243DEC782848FCAB001281AD /* MonthlyTransactionDetailed */,
243DEC762848FB45001281AD /* MonthlyTransactionSettings */,
739DFF7925DC1E23005BD5C8 /* Authenticate */,
738B1C8E25C680C50067407B /* About */,
738B1C8925C67D790067407B /* ExpenseFilter */,
Expand Down Expand Up @@ -199,6 +229,7 @@
isa = PBXGroup;
children = (
738B1C3225C660140067407B /* ExpenseCD.swift */,
24DF38B52846B7BC006CA05F /* MonthlyTransactionCD.swift */,
);
path = CoreData;
sourceTree = "<group>";
Expand Down Expand Up @@ -450,12 +481,15 @@
files = (
738B1C3825C661750067407B /* ExpenseView.swift in Sources */,
73998C5525DA5578007D735B /* AttachmentHandler.swift in Sources */,
243DEC7A2848FCC1001281AD /* MonthlyTransactionDetailedView.swift in Sources */,
738B1C8425C6764B0067407B /* ExpenseDetailedViewModel.swift in Sources */,
738B1C9025C680D20067407B /* AboutView.swift in Sources */,
24AE70432847F81100B2BD49 /* MonthlyTransactionSettingsView.swift in Sources */,
738B1C4F25C663180067407B /* DateExtension.swift in Sources */,
738B1C5225C6632F0067407B /* HelperMethods.swift in Sources */,
738B1C2425C65E060067407B /* Expenso.xcdatamodeld in Sources */,
738B1C8B25C67D880067407B /* ExpenseFilterView.swift in Sources */,
24DF38B62846B7BC006CA05F /* MonthlyTransactionCD.swift in Sources */,
739DFF7B25DC1E3C005BD5C8 /* AuthenticateView.swift in Sources */,
738B1C3325C660140067407B /* ExpenseCD.swift in Sources */,
738B1C4325C6629D0067407B /* ColorExtension.swift in Sources */,
Expand All @@ -474,8 +508,10 @@
738B1C2F25C65EF70067407B /* Configs.swift in Sources */,
73E688DA25FF32790000462A /* ChartView.swift in Sources */,
736C721B25CFE8E200720DEA /* LottieView.swift in Sources */,
243DEC7C2848FF55001281AD /* MonthlyTransactionDetailedViewModel.swift in Sources */,
738B1C7725C675200067407B /* AddExpenseView.swift in Sources */,
738B1C8025C6763E0067407B /* ExpenseDetailedView.swift in Sources */,
2463680C284A35CE007FC25F /* EditMonthlyTransactionView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
12 changes: 11 additions & 1 deletion Expenso/Expenso.xcdatamodeld/Expenso.xcdatamodel/contents
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20B28" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21D62" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="ExpenseCD" representedClassName=".ExpenseCD" syncable="YES">
<attribute name="amount" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
Expand All @@ -11,7 +11,17 @@
<attribute name="type" optional="YES" attributeType="String"/>
<attribute name="updatedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<entity name="MonthlyTransactionCD" representedClassName=".MonthlyTransactionCD" syncable="YES">
<attribute name="amount" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="imageAttached" optional="YES" attributeType="Binary"/>
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="tag" optional="YES" attributeType="String"/>
<attribute name="title" optional="YES" attributeType="String"/>
<attribute name="type" optional="YES" attributeType="String"/>
<attribute name="usingDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
<elements>
<element name="ExpenseCD" positionX="-63" positionY="-9" width="128" height="164"/>
<element name="MonthlyTransactionCD" positionX="-63" positionY="63" width="128" height="134"/>
</elements>
</model>
27 changes: 27 additions & 0 deletions Expenso/Library/CoreData/MonthlyTransactionCD.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// MonthlyExpenseCD.swift
// Expenso
//
// Created by Hendrik Steen on 31.05.22.
//

import Foundation
import CoreData

public class MonthlyTransactionCD: NSManagedObject, Identifiable {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of new model, we can use the existing model ExpenseCD with frequency enum. Which can store values such as { onetime, monthly }. Use this frequency val to query.

Also we don't need a separate button (Add income every month) to create recurring expense. Let's add a toggle which can update the frequency value.

IMG_514CF50EB555-1

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a possibility that this feature will be included in the app in the App Store? I ask myself this question because you mentioned that this is a different approach. However, I will start working on it.

@NSManaged public var type: String?
@NSManaged public var title: String?
@NSManaged public var tag: String?
@NSManaged public var note: String?
@NSManaged public var amount: Double
@NSManaged public var imageAttached: Data?
@NSManaged public var usingDate: Date?
}

extension MonthlyTransactionCD {
static func getAllMonthlyExpenseData() -> NSFetchRequest<MonthlyTransactionCD> {
let request: NSFetchRequest<MonthlyTransactionCD> = MonthlyTransactionCD.fetchRequest() as! NSFetchRequest<MonthlyTransactionCD>
request.sortDescriptors = []
return request
}
}
10 changes: 10 additions & 0 deletions Expenso/Screens/AddExpense/AddExpenseView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ struct AddExpenseView: View {
VStack {
Spacer()
VStack {
if viewModel.expenseObj == nil {
Button(action: { viewModel.saveMonthlyTransaction(managedObjectContext: managedObjectContext) }, label: {
HStack {
Spacer()
TextView(text: "\(viewModel.getButtText()) every Month", type: .button).foregroundColor(.white)
Spacer()
}
})
.padding(.vertical, 12).background(Color.main_color).cornerRadius(8)
}
Button(action: { viewModel.saveTransaction(managedObjectContext: managedObjectContext) }, label: {
HStack {
Spacer()
Expand Down
187 changes: 163 additions & 24 deletions Expenso/Screens/AddExpense/AddExpenseViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import CoreData
class AddExpenseViewModel: ObservableObject {

var expenseObj: ExpenseCD?
var monthlyTransactionObj: MonthlyTransactionCD?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update this to use single model (refer above comment).


@Published var title = ""
@Published var amount = ""
Expand All @@ -31,36 +32,65 @@ class AddExpenseViewModel: ObservableObject {
@Published var showAlert = false
@Published var closePresenter = false

init(expenseObj: ExpenseCD? = nil) {

self.expenseObj = expenseObj
self.title = expenseObj?.title ?? ""
if let expenseObj = expenseObj {
self.amount = String(expenseObj.amount)
self.typeTitle = expenseObj.type == TRANS_TYPE_INCOME ? "Income" : "Expense"
init(expenseObj: ExpenseCD? = nil, monthlyTransactionObj: MonthlyTransactionCD? = nil) {
if monthlyTransactionObj != nil {

self.monthlyTransactionObj = monthlyTransactionObj
self.title = monthlyTransactionObj?.title ?? ""
if let monthlyTransactionObj = monthlyTransactionObj {
self.amount = String(monthlyTransactionObj.amount)
self.typeTitle = monthlyTransactionObj.type == TRANS_TYPE_INCOME ? "Income" : "Expense"
} else {
self.amount = ""
self.typeTitle = "Income"
}
self.occuredOn = monthlyTransactionObj?.usingDate ?? Date()
self.note = monthlyTransactionObj?.note ?? ""
self.tagTitle = getTransTagTitle(transTag: monthlyTransactionObj?.tag ?? TRANS_TAG_TRANSPORT)
self.selectedType = monthlyTransactionObj?.type ?? TRANS_TYPE_INCOME
self.selectedTag = monthlyTransactionObj?.tag ?? TRANS_TAG_TRANSPORT
if let data = monthlyTransactionObj?.imageAttached {
self.imageAttached = UIImage(data: data)
}

AttachmentHandler.shared.imagePickedBlock = { [weak self] image in
self?.imageUpdated = true
self?.imageAttached = image
}

} else {
self.amount = ""
self.typeTitle = "Income"
}
self.occuredOn = expenseObj?.occuredOn ?? Date()
self.note = expenseObj?.note ?? ""
self.tagTitle = getTransTagTitle(transTag: expenseObj?.tag ?? TRANS_TAG_TRANSPORT)
self.selectedType = expenseObj?.type ?? TRANS_TYPE_INCOME
self.selectedTag = expenseObj?.tag ?? TRANS_TAG_TRANSPORT
if let data = expenseObj?.imageAttached {
self.imageAttached = UIImage(data: data)

self.expenseObj = expenseObj
self.title = expenseObj?.title ?? ""
if let expenseObj = expenseObj {
self.amount = String(expenseObj.amount)
self.typeTitle = expenseObj.type == TRANS_TYPE_INCOME ? "Income" : "Expense"
} else {
self.amount = ""
self.typeTitle = "Income"
}
self.occuredOn = expenseObj?.occuredOn ?? Date()
self.note = expenseObj?.note ?? ""
self.tagTitle = getTransTagTitle(transTag: expenseObj?.tag ?? TRANS_TAG_TRANSPORT)
self.selectedType = expenseObj?.type ?? TRANS_TYPE_INCOME
self.selectedTag = expenseObj?.tag ?? TRANS_TAG_TRANSPORT
if let data = expenseObj?.imageAttached {
self.imageAttached = UIImage(data: data)
}

AttachmentHandler.shared.imagePickedBlock = { [weak self] image in
self?.imageUpdated = true
self?.imageAttached = image
}

}

AttachmentHandler.shared.imagePickedBlock = { [weak self] image in
self?.imageUpdated = true
self?.imageAttached = image
}
}

func getButtText() -> String {
if selectedType == TRANS_TYPE_INCOME { return "\(expenseObj == nil ? "ADD" : "EDIT") INCOME" }
else if selectedType == TRANS_TYPE_EXPENSE { return "\(expenseObj == nil ? "ADD" : "EDIT") EXPENSE" }
else { return "\(expenseObj == nil ? "ADD" : "EDIT") TRANSACTION" }
if selectedType == TRANS_TYPE_INCOME { return "\((expenseObj != nil || monthlyTransactionObj != nil) ? "EDIT" : "ADD") INCOME" }
else if selectedType == TRANS_TYPE_EXPENSE { return "\((expenseObj != nil || monthlyTransactionObj != nil) ? "EDIT" : "ADD") EXPENSE" }
else { return "\((expenseObj != nil || monthlyTransactionObj != nil) ? "EDIT" : "ADD") TRANSACTION" }
}

func attachImage() { AttachmentHandler.shared.showAttachmentActionSheet() }
Expand Down Expand Up @@ -139,4 +169,113 @@ class AddExpenseViewModel: ObservableObject {
try managedObjectContext.save(); closePresenter = true
} catch { alertMsg = "\(error)"; showAlert = true }
}


func saveMonthlyTransaction(managedObjectContext: NSManagedObjectContext) {
let expense: MonthlyTransactionCD
let titleStr = title.trimmingCharacters(in: .whitespacesAndNewlines)
let amountStr = amount.trimmingCharacters(in: .whitespacesAndNewlines)

if titleStr.isEmpty || titleStr == "" {
alertMsg = "Enter Title"; showAlert = true
return
}
if amountStr.isEmpty || amountStr == "" {
alertMsg = "Enter Amount"; showAlert = true
return
}
guard let amount = Double(amountStr) else {
alertMsg = "Enter valid number"; showAlert = true
return
}
guard amount >= 0 else {
alertMsg = "Amount can't be negative"; showAlert = true
return
}
guard amount <= 1000000000 else {
alertMsg = "Enter a smaller amount"; showAlert = true
return
}

if monthlyTransactionObj != nil {

expense = monthlyTransactionObj!

// if let image = imageAttached {
// if imageUpdated {
// if let _ = expense.imageAttached {
// // Delete Previous Image from CoreData
// }
// expense.imageAttached = image.jpegData(compressionQuality: 1.0)
// }
// } else {
// if let _ = expense.imageAttached {
// // Delete Previous Image from CoreData
// }
// expense.imageAttached = nil
// }

} else {
expense = MonthlyTransactionCD(context: managedObjectContext)
// if let image = imageAttached {
// expense.imageAttached = image.jpegData(compressionQuality: 1.0)
// }
}
expense.usingDate = occuredOn
expense.type = selectedType
expense.title = titleStr
expense.tag = selectedTag
expense.note = note
expense.amount = amount
do {
try managedObjectContext.save()
closePresenter = true
} catch { alertMsg = "\(error)"; showAlert = true }
}

func deleteMonthlyTransaction(managedObjectContext: NSManagedObjectContext) {
guard let monthlyTransactionObj = monthlyTransactionObj else { return }
managedObjectContext.delete(monthlyTransactionObj)
do {
try managedObjectContext.save(); closePresenter = true
} catch { alertMsg = "\(error)"; showAlert = true }
}

func checkAllMonthlyExpenses(managedObjectContext: NSManagedObjectContext, request: NSFetchRequest<MonthlyTransactionCD>) {
do {
let allMonthlyExpenses = try? managedObjectContext.fetch(request)
if allMonthlyExpenses == nil {
print("error")
return
}
for expense in allMonthlyExpenses! {
print(expense.usingDate ?? Date())
if expense.usingDate == nil {
continue
}
if Calendar.current.isDateInToday(expense.usingDate!) || Date() > expense.usingDate! {
if let date = Calendar.current.date(byAdding: .month, value: 1, to: expense.usingDate!) {
expense.usingDate = date
print("date used")
let newExpense = ExpenseCD(context: managedObjectContext)
newExpense.amount = expense.amount
newExpense.createdAt = Date()
//newExpense.imageAttached = expense.imageAttached
newExpense.note = expense.note
newExpense.occuredOn = Date()
newExpense.tag = expense.tag
newExpense.title = expense.title
newExpense.type = expense.type
newExpense.updatedAt = Date()
try managedObjectContext.save()
}
} else {
print("not current date or later date")
}
}
} catch {
print(error)
}
}

}
Loading