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
Prev Previous commit
Next Next commit
monthly Transactions with one entity
comhendrik committed Jun 13, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit a6eb61e7f804191d8ca662f7716b57407a59aca5
12 changes: 12 additions & 0 deletions Expenso.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
24A10DD02856809200C0BA52 /* Expenso.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 24A10DCE2856809200C0BA52 /* Expenso.xcdatamodeld */; };
24D9168D285735920025227B /* MonthlyTransactionSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24D9168C285735920025227B /* MonthlyTransactionSettingsView.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 */; };
@@ -54,6 +55,7 @@
/* Begin PBXFileReference section */
1B0E373FB89DD0864398FEE7 /* Pods_Expenso.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Expenso.framework; sourceTree = BUILT_PRODUCTS_DIR; };
24A10DCF2856809200C0BA52 /* Expenso.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Expenso.xcdatamodel; sourceTree = "<group>"; };
24D9168C285735920025227B /* MonthlyTransactionSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthlyTransactionSettingsView.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>"; };
@@ -112,9 +114,18 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
24D9168B285735730025227B /* MonthlyTransactionSettings */ = {
isa = PBXGroup;
children = (
24D9168C285735920025227B /* MonthlyTransactionSettingsView.swift */,
);
path = MonthlyTransactionSettings;
sourceTree = "<group>";
};
733763A827120DB600AA983A /* Screens */ = {
isa = PBXGroup;
children = (
24D9168B285735730025227B /* MonthlyTransactionSettings */,
739DFF7925DC1E23005BD5C8 /* Authenticate */,
738B1C8E25C680C50067407B /* About */,
738B1C8925C67D790067407B /* ExpenseFilter */,
@@ -463,6 +474,7 @@
738B1C3C25C662580067407B /* Models.swift in Sources */,
753CBDD325F36864005762B8 /* BiometricAuthUtility.swift in Sources */,
738B1C7325C674320067407B /* ExpenseSettingsView.swift in Sources */,
24D9168D285735920025227B /* MonthlyTransactionSettingsView.swift in Sources */,
75C6B48025F37FD20079BCFC /* AuthenticationViewModel.swift in Sources */,
738B1C4C25C663060067407B /* DismissKeyboard.swift in Sources */,
738B1C4625C662D90067407B /* TextView.swift in Sources */,
13 changes: 2 additions & 11 deletions Expenso/Expenso.xcdatamodeld/Expenso.xcdatamodel/contents
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
<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"/>
<attribute name="frequencyValue" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="imageAttached" optional="YES" attributeType="Binary"/>
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="occuredOn" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
@@ -11,17 +12,7 @@
<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"/>
<element name="ExpenseCD" positionX="-63" positionY="-9" width="128" height="179"/>
</elements>
</model>
8 changes: 6 additions & 2 deletions Expenso/Library/CoreData/ExpenseCD.swift
Original file line number Diff line number Diff line change
@@ -37,7 +37,12 @@ public class ExpenseCD: NSManagedObject, Identifiable {
@NSManaged public var amount: Double
@NSManaged public var imageAttached: Data?

@NSManaged public var frequencyValue: Frequency
@NSManaged public var frequencyValue: Int16

var frequency: Frequency {
get { return Frequency.init(rawValue: frequencyValue) ?? .onetime}
set { frequencyValue = newValue.rawValue}
}
}

extension ExpenseCD {
@@ -62,7 +67,6 @@ extension ExpenseCD {
static func sortExpenseDataByFrequency(sortBy: ExpenseCDSort = .occuredOn, frequency: Frequency, ascending: Bool = true) -> NSFetchRequest<ExpenseCD> {
let request: NSFetchRequest<ExpenseCD> = ExpenseCD.fetchRequest() as! NSFetchRequest<ExpenseCD>
let sortDescriptor = NSSortDescriptor(key: sortBy.rawValue, ascending: ascending)
print(frequency.rawValue)
let predicate = NSPredicate(format: "frequencyValue == %i", frequency.rawValue)
request.predicate = predicate
request.sortDescriptors = [sortDescriptor]
28 changes: 11 additions & 17 deletions Expenso/Screens/AddExpense/AddExpenseView.swift
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@ struct AddExpenseView: View {

@StateObject var viewModel: AddExpenseViewModel

var hasToggle: Bool = true

let typeOptions = [
DropdownOption(key: TRANS_TYPE_INCOME, val: "Income"),
DropdownOption(key: TRANS_TYPE_EXPENSE, val: "Expense")
@@ -114,12 +116,15 @@ struct AddExpenseView: View {
.background(Color.secondary_color)
.cornerRadius(4)

Toggle("monthly", isOn: $viewModel.monthlyFrequency)
.padding(5)
.accentColor(Color.text_primary_color)
.frame(height: 50).padding(.leading, 16)
.background(Color.secondary_color)
.cornerRadius(4)
if hasToggle {
//MARK: User can define Transaction as monthly when it is created or change it to onetime, when in the Monthly Transaction View. The User is not allowed to change the state from onetime to monthly in the recent transaction list to prevent that the transaction is done more than one time monthly. If it should be monthly the user has to created a new Expense
Toggle("monthly", isOn: $viewModel.monthlyFrequency)
.padding(5)
.accentColor(Color.text_primary_color)
.frame(height: 50).padding(.leading, 16)
.background(Color.secondary_color)
.cornerRadius(4)
}

Button(action: { viewModel.attachImage() }, label: {
HStack {
@@ -154,7 +159,6 @@ struct AddExpenseView: View {
}

Spacer().frame(height: 150)
//Spacer()
}
.frame(maxWidth: .infinity).padding(.horizontal, 8)
.alert(isPresented: $viewModel.showAlert,
@@ -166,16 +170,6 @@ 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()
40 changes: 34 additions & 6 deletions Expenso/Screens/AddExpense/AddExpenseViewModel.swift
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ class AddExpenseViewModel: ObservableObject {
self.tagTitle = getTransTagTitle(transTag: expenseObj?.tag ?? TRANS_TAG_TRANSPORT)
self.selectedType = expenseObj?.type ?? TRANS_TYPE_INCOME
self.selectedTag = expenseObj?.tag ?? TRANS_TAG_TRANSPORT
if expenseObj?.frequencyValue == .monthly {
if expenseObj?.frequency == .monthly {
monthlyFrequency = true
}
if let data = expenseObj?.imageAttached {
@@ -63,9 +63,9 @@ class AddExpenseViewModel: ObservableObject {
}

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

func attachImage() { AttachmentHandler.shared.showAttachmentActionSheet() }
@@ -132,9 +132,9 @@ class AddExpenseViewModel: ObservableObject {
expense.note = note
expense.amount = amount
if monthlyFrequency {
expense.frequencyValue = .monthly
expense.frequency = .monthly
} else {
expense.frequencyValue = .onetime
expense.frequency = .onetime
}
do {
try managedObjectContext.save()
@@ -150,4 +150,32 @@ class AddExpenseViewModel: ObservableObject {
} catch { alertMsg = "\(error)"; showAlert = true }
}

func repeatTransaction(managedObjectContext: NSManagedObjectContext) {
do {
//TODO: Make sure that only the transactions from one month ago are re done.
let request = ExpenseCD.sortExpenseDataByFrequency(frequency: Frequency.monthly)
let monthlyExpenses = try managedObjectContext.fetch(request)
for expense in monthlyExpenses {
if let compareDate = Calendar.current.date(byAdding: .month, value: 1, to: expense.occuredOn ?? Date()) {
if Calendar.current.isDateInToday(compareDate) || compareDate < Date() {
selectedType = expense.type ?? TRANS_TYPE_INCOME
selectedTag = expense.tag ?? TRANS_TAG_OTHERS
title = expense.title ?? ""
occuredOn = compareDate
if let image = imageAttached {
expense.imageAttached = image.jpegData(compressionQuality: 1.0)
}
note = expense.note ?? ""
amount = String(expense.amount)
monthlyFrequency = true

//change frequency of old object to onetime
expense.frequency = .onetime
self.saveTransaction(managedObjectContext: managedObjectContext)
}
}
}
try managedObjectContext.save(); closePresenter = true
} catch { alertMsg = "\(error)"; showAlert = true; print(error) }
}
}
14 changes: 9 additions & 5 deletions Expenso/Screens/Expense/ExpenseView.swift
Original file line number Diff line number Diff line change
@@ -12,7 +12,9 @@ struct ExpenseView: View {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
// CoreData
@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(fetchRequest: ExpenseCD.sortExpenseDataByFrequency(frequency: Frequency.onetime)) var expense: FetchedResults<ExpenseCD>

//MARK: Do you still need this Fetch Request I can't see any use case
@FetchRequest(fetchRequest: ExpenseCD.getAllExpenseData(sortBy: ExpenseCDSort.occuredOn, ascending: false)) var expense: FetchedResults<ExpenseCD>

@State private var filter: ExpenseCDFilterTime = .all
@State private var showFilterSheet = false
@@ -62,6 +64,11 @@ struct ExpenseView: View {
NavigationLink(destination: NavigationLazyView(AddExpenseView(viewModel: AddExpenseViewModel())),
label: { Image("plus_icon").resizable().frame(width: 32.0, height: 32.0) })
.padding().background(Color.main_color).cornerRadius(35)
Button(action: {
AddExpenseViewModel().repeatTransaction(managedObjectContext: managedObjectContext)
}, label: {
Text("Test data")
})
}
}.padding()
}
@@ -70,9 +77,6 @@ struct ExpenseView: View {
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.onAppear() {
AddExpenseViewModel().checkAllMonthlyExpenses(managedObjectContext: managedObjectContext, request: MonthlyTransactionCD.getAllMonthlyExpenseData())
}
}
}

@@ -139,7 +143,7 @@ struct ExpenseMainView: View {
}.padding(4)

ForEach(self.fetchRequest.wrappedValue) { expenseObj in
NavigationLink(destination: ExpenseDetailedView(expenseObj: expenseObj), label: { ExpenseTransView(expenseObj: expenseObj) })
NavigationLink(destination: ExpenseDetailedView(expenseObj: expenseObj, editViewHasToggle: false), label: { ExpenseTransView(expenseObj: expenseObj) })
}
}

7 changes: 5 additions & 2 deletions Expenso/Screens/ExpenseDetailed/ExpenseDetailedView.swift
Original file line number Diff line number Diff line change
@@ -18,8 +18,11 @@ struct ExpenseDetailedView: View {

@State private var confirmDelete = false

init(expenseObj: ExpenseCD) {
var editViewHasToggle: Bool

init(expenseObj: ExpenseCD, editViewHasToggle: Bool) {
viewModel = ExpenseDetailedViewModel(expenseObj: expenseObj)
self.editViewHasToggle = editViewHasToggle
}

var body: some View {
@@ -74,7 +77,7 @@ struct ExpenseDetailedView: View {
Spacer()
HStack {
Spacer()
NavigationLink(destination: AddExpenseView(viewModel: AddExpenseViewModel(expenseObj: viewModel.expenseObj)), label: {
NavigationLink(destination: AddExpenseView(viewModel: AddExpenseViewModel(expenseObj: viewModel.expenseObj), hasToggle: editViewHasToggle), label: {
Image("pencil_icon").resizable().frame(width: 28.0, height: 28.0)
Text("Edit").modifier(InterFont(.semiBold, size: 18)).foregroundColor(.white)
})
2 changes: 1 addition & 1 deletion Expenso/Screens/ExpenseFilter/ExpenseFilterView.swift
Original file line number Diff line number Diff line change
@@ -168,7 +168,7 @@ struct ExpenseFilterTransList: View {

var body: some View {
ForEach(self.fetchRequest.wrappedValue) { expenseObj in
NavigationLink(destination: ExpenseDetailedView(expenseObj: expenseObj), label: { ExpenseTransView(expenseObj: expenseObj) })
NavigationLink(destination: ExpenseDetailedView(expenseObj: expenseObj, editViewHasToggle: false), label: { ExpenseTransView(expenseObj: expenseObj) })
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// MonthlyTransactionSettings.swift
// Expenso
//
// Created by Hendrik Steen on 13.06.22.
//

import SwiftUI

struct MonthlyTransactionSettingsView: View {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

@FetchRequest(fetchRequest: ExpenseCD.sortExpenseDataByFrequency(frequency: Frequency.monthly)) var monthlyExpenses: FetchedResults<ExpenseCD>
var body: some View {
NavigationView {
ZStack {
Color.primary_color.edgesIgnoringSafeArea(.all)

VStack {
ToolbarModelView(title: "Monthly Transaction") { self.presentationMode.wrappedValue.dismiss() }
ScrollView {
ForEach(monthlyExpenses) { expenseObj in
NavigationLink(destination: ExpenseDetailedView(expenseObj: expenseObj, editViewHasToggle: true), label: { ExpenseTransView(expenseObj: expenseObj) })
}
}
.padding(.horizontal, 8)
}.edgesIgnoringSafeArea(.all)
}
.navigationBarHidden(true)

}
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)

}

}

struct MonthlyTransactionSettingsView_Previews: PreviewProvider {
static var previews: some View {
MonthlyTransactionSettingsView()
}
}