From e9b3d963f529112ce56b191ed797dde8c5b6b4b8 Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Thu, 9 Feb 2023 22:21:02 +0800 Subject: [PATCH 01/12] feat(iExpense): add challenge 1 --- 10-iExpense/iExpense/iExpense/ContentView.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/10-iExpense/iExpense/iExpense/ContentView.swift b/10-iExpense/iExpense/iExpense/ContentView.swift index 7e5a620..0689460 100644 --- a/10-iExpense/iExpense/iExpense/ContentView.swift +++ b/10-iExpense/iExpense/iExpense/ContentView.swift @@ -10,6 +10,11 @@ struct ContentView: View { @StateObject var expenses = Expenses() @State private var showingAddExpense = false + // challenge 1: I'll use locale currency instead of user's preferred currency ^,..,^ + var localCurrency: FloatingPointFormatStyle.Currency { + .currency(code: Locale.current.currency?.identifier ?? "USD") + } + var body: some View { NavigationView { List { @@ -23,7 +28,7 @@ struct ContentView: View { Spacer() - Text(item.amount, format: .currency(code: "USD")) + Text(item.amount, format: localCurrency) } } .onDelete { IndexSet in From 409c3033a146811056f05fa56c52dcf17f08294b Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Thu, 9 Feb 2023 22:42:39 +0800 Subject: [PATCH 02/12] feat(iExpense): add challenge 2 --- 10-iExpense/iExpense/iExpense/ContentView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/10-iExpense/iExpense/iExpense/ContentView.swift b/10-iExpense/iExpense/iExpense/ContentView.swift index 0689460..71e14f3 100644 --- a/10-iExpense/iExpense/iExpense/ContentView.swift +++ b/10-iExpense/iExpense/iExpense/ContentView.swift @@ -29,6 +29,7 @@ struct ContentView: View { Spacer() Text(item.amount, format: localCurrency) + .foregroundColor(item.amount <= 10 ? .green : item.amount <= 100 ? .yellow : .red) // challenge 2 } } .onDelete { IndexSet in From 93bfd1f72b2c282802a38262afb3e8645579b0a5 Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 02:53:43 +0800 Subject: [PATCH 03/12] feat(iExpense): add challenge 3 --- .../iExpense.xcodeproj/project.pbxproj | 4 ++ .../iExpense/iExpense/ContentView.swift | 54 +++++++++++++------ .../iExpense/iExpense/HeaderFont.swift | 36 +++++++++++++ 3 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 10-iExpense/iExpense/iExpense/HeaderFont.swift diff --git a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj index 1e4b8f0..5102236 100644 --- a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj +++ b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 2C51729A2993FA4F00461260 /* ExpenseItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5172992993FA4F00461260 /* ExpenseItem.swift */; }; 2C51729C2993FAB600461260 /* Expenses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C51729B2993FAB600461260 /* Expenses.swift */; }; 2C51729E2994114800461260 /* AddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C51729D2994114800461260 /* AddView.swift */; }; + 2C6389F82995458D00210EA9 /* HeaderFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6389F72995458D00210EA9 /* HeaderFont.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -25,6 +26,7 @@ 2C5172992993FA4F00461260 /* ExpenseItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpenseItem.swift; sourceTree = ""; }; 2C51729B2993FAB600461260 /* Expenses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expenses.swift; sourceTree = ""; }; 2C51729D2994114800461260 /* AddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddView.swift; sourceTree = ""; }; + 2C6389F72995458D00210EA9 /* HeaderFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderFont.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -59,6 +61,7 @@ children = ( 2C145CAF298BF29700813CBC /* iExpenseApp.swift */, 2C145CB1298BF29700813CBC /* ContentView.swift */, + 2C6389F72995458D00210EA9 /* HeaderFont.swift */, 2C51729D2994114800461260 /* AddView.swift */, 2C5172992993FA4F00461260 /* ExpenseItem.swift */, 2C51729B2993FAB600461260 /* Expenses.swift */, @@ -149,6 +152,7 @@ 2C51729C2993FAB600461260 /* Expenses.swift in Sources */, 2C145CB2298BF29700813CBC /* ContentView.swift in Sources */, 2C51729A2993FA4F00461260 /* ExpenseItem.swift in Sources */, + 2C6389F82995458D00210EA9 /* HeaderFont.swift in Sources */, 2C51729E2994114800461260 /* AddView.swift in Sources */, 2C145CB0298BF29700813CBC /* iExpenseApp.swift in Sources */, ); diff --git a/10-iExpense/iExpense/iExpense/ContentView.swift b/10-iExpense/iExpense/iExpense/ContentView.swift index 71e14f3..f8d7571 100644 --- a/10-iExpense/iExpense/iExpense/ContentView.swift +++ b/10-iExpense/iExpense/iExpense/ContentView.swift @@ -10,6 +10,8 @@ struct ContentView: View { @StateObject var expenses = Expenses() @State private var showingAddExpense = false + let types = ["Business", "Personal"] + // challenge 1: I'll use locale currency instead of user's preferred currency ^,..,^ var localCurrency: FloatingPointFormatStyle.Currency { .currency(code: Locale.current.currency?.identifier ?? "USD") @@ -18,25 +20,34 @@ struct ContentView: View { var body: some View { NavigationView { List { - ForEach(expenses.items) { item in - HStack { - VStack(alignment: .leading) { - Text(item.name) - .font(.headline) - Text(item.type) + ForEach(types, id: \.self) { type in + Section { + // challenge 3 + ForEach(expensesFilter(type)) { item in + HStack { + VStack(alignment: .leading) { + Text(item.name) + .font(.headline) + Text(item.type) + } + + Spacer() + + Text(item.amount, format: localCurrency) + .foregroundColor(item.amount <= 10 ? .green : item.amount <= 100 ? .yellow : .red) // challenge 2 + } + } + .onDelete { IndexSet in + removeItems(at: IndexSet, for: type) + } + } header: { + if expensesFilter(type).count != 0 { + HeaderFont(type) } - - Spacer() - - Text(item.amount, format: localCurrency) - .foregroundColor(item.amount <= 10 ? .green : item.amount <= 100 ? .yellow : .red) // challenge 2 } } - .onDelete { IndexSet in - removeItems(at: IndexSet) - } } - .navigationTitle("iExpense") + .navigationTitle("iExpense 💸") .toolbar { Button { showingAddExpense = true @@ -50,9 +61,18 @@ struct ContentView: View { } } - func removeItems(at offsets: IndexSet) { - expenses.items.remove(atOffsets: offsets) + // challenge 3 + func expensesFilter(_ type: String) -> [ExpenseItem] { + expenses.items.filter { $0.type == type } } + + func removeItems(at offsets: IndexSet, for type: String) { + let chosenElement = expensesFilter(type)[offsets.first!] + let uuid = chosenElement.id + let index = expenses.items.firstIndex(where: { $0.id == uuid })! + expenses.items.remove(at: index) + } + } struct ContentView_Previews: PreviewProvider { diff --git a/10-iExpense/iExpense/iExpense/HeaderFont.swift b/10-iExpense/iExpense/iExpense/HeaderFont.swift new file mode 100644 index 0000000..9fa4027 --- /dev/null +++ b/10-iExpense/iExpense/iExpense/HeaderFont.swift @@ -0,0 +1,36 @@ +// Date: 2/9/23 +// +// Author: Zai Santillan +// Github: @plskz + + +import SwiftUI + +struct HeaderFont: View { + let types = ["Business", "Personal"] + var headerTitle: String + + init(_ headerTitle: String) { + self.headerTitle = headerTitle + } + + var body: some View { + Text("\(headerTitle) \(headerTitle == "Personal" ? "🛍️" : "💼")") + .font(.title2.bold()) + .foregroundColor(.blue) + .textCase(nil) + } +} + +struct HeaderFont_Previews: PreviewProvider { + static let types = ["Business", "Personal"] + + static var previews: some View { + VStack { + ForEach(types, id: \.self) { type in + HeaderFont(type) + } + .padding() + } + } +} From b5e7206b219caabc866b9623b78e0a95017a3f8e Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 02:58:26 +0800 Subject: [PATCH 04/12] chore(iExpense): improve challenge 1 --- .../iExpense/iExpense.xcodeproj/project.pbxproj | 4 ++++ 10-iExpense/iExpense/iExpense/ContentView.swift | 7 +------ .../iExpense/Extension-LocalCurrency.swift | 14 ++++++++++++++ 10-iExpense/iExpense/iExpense/File.swift | 7 +++++++ 4 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 10-iExpense/iExpense/iExpense/Extension-LocalCurrency.swift create mode 100644 10-iExpense/iExpense/iExpense/File.swift diff --git a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj index 5102236..c3284d3 100644 --- a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj +++ b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 2C51729C2993FAB600461260 /* Expenses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C51729B2993FAB600461260 /* Expenses.swift */; }; 2C51729E2994114800461260 /* AddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C51729D2994114800461260 /* AddView.swift */; }; 2C6389F82995458D00210EA9 /* HeaderFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6389F72995458D00210EA9 /* HeaderFont.swift */; }; + 2C9082992995792C003FEB23 /* Extension-LocalCurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -27,6 +28,7 @@ 2C51729B2993FAB600461260 /* Expenses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expenses.swift; sourceTree = ""; }; 2C51729D2994114800461260 /* AddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddView.swift; sourceTree = ""; }; 2C6389F72995458D00210EA9 /* HeaderFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderFont.swift; sourceTree = ""; }; + 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension-LocalCurrency.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -61,6 +63,7 @@ children = ( 2C145CAF298BF29700813CBC /* iExpenseApp.swift */, 2C145CB1298BF29700813CBC /* ContentView.swift */, + 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */, 2C6389F72995458D00210EA9 /* HeaderFont.swift */, 2C51729D2994114800461260 /* AddView.swift */, 2C5172992993FA4F00461260 /* ExpenseItem.swift */, @@ -155,6 +158,7 @@ 2C6389F82995458D00210EA9 /* HeaderFont.swift in Sources */, 2C51729E2994114800461260 /* AddView.swift in Sources */, 2C145CB0298BF29700813CBC /* iExpenseApp.swift in Sources */, + 2C9082992995792C003FEB23 /* Extension-LocalCurrency.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/10-iExpense/iExpense/iExpense/ContentView.swift b/10-iExpense/iExpense/iExpense/ContentView.swift index f8d7571..c881746 100644 --- a/10-iExpense/iExpense/iExpense/ContentView.swift +++ b/10-iExpense/iExpense/iExpense/ContentView.swift @@ -12,11 +12,6 @@ struct ContentView: View { let types = ["Business", "Personal"] - // challenge 1: I'll use locale currency instead of user's preferred currency ^,..,^ - var localCurrency: FloatingPointFormatStyle.Currency { - .currency(code: Locale.current.currency?.identifier ?? "USD") - } - var body: some View { NavigationView { List { @@ -33,7 +28,7 @@ struct ContentView: View { Spacer() - Text(item.amount, format: localCurrency) + Text(item.amount, format: .localCurrency) // challenge 1 .foregroundColor(item.amount <= 10 ? .green : item.amount <= 100 ? .yellow : .red) // challenge 2 } } diff --git a/10-iExpense/iExpense/iExpense/Extension-LocalCurrency.swift b/10-iExpense/iExpense/iExpense/Extension-LocalCurrency.swift new file mode 100644 index 0000000..cf5686e --- /dev/null +++ b/10-iExpense/iExpense/iExpense/Extension-LocalCurrency.swift @@ -0,0 +1,14 @@ +// Date: 2/10/23 +// +// Author: Zai Santillan +// Github: @plskz + + +import Foundation + +extension FormatStyle where Self == FloatingPointFormatStyle.Currency { + /// challenge 1: I'll use locale currency instead of user's preferred currency ^,..,^ + static var localCurrency: Self { + .currency(code: Locale.current.currency?.identifier ?? "USD") + } +} diff --git a/10-iExpense/iExpense/iExpense/File.swift b/10-iExpense/iExpense/iExpense/File.swift new file mode 100644 index 0000000..d31bed2 --- /dev/null +++ b/10-iExpense/iExpense/iExpense/File.swift @@ -0,0 +1,7 @@ +// Date: 2/10/23 +// +// Author: Zai Santillan +// Github: @plskz + + +import Foundation From 9486313d3f37c0f1b9cb067ccaae33c37d20d429 Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 03:09:49 +0800 Subject: [PATCH 05/12] chore(iExpense): improve challenge 2 --- .../iExpense/iExpense.xcodeproj/project.pbxproj | 4 ++++ 10-iExpense/iExpense/iExpense/ContentView.swift | 2 +- .../iExpense/Extension-ExpenseStyling.swift | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 10-iExpense/iExpense/iExpense/Extension-ExpenseStyling.swift diff --git a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj index c3284d3..bf178ef 100644 --- a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj +++ b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 2C51729E2994114800461260 /* AddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C51729D2994114800461260 /* AddView.swift */; }; 2C6389F82995458D00210EA9 /* HeaderFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6389F72995458D00210EA9 /* HeaderFont.swift */; }; 2C9082992995792C003FEB23 /* Extension-LocalCurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */; }; + 2C90829B29957A9B003FEB23 /* Extension-ExpenseStyling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C90829A29957A9B003FEB23 /* Extension-ExpenseStyling.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -29,6 +30,7 @@ 2C51729D2994114800461260 /* AddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddView.swift; sourceTree = ""; }; 2C6389F72995458D00210EA9 /* HeaderFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderFont.swift; sourceTree = ""; }; 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension-LocalCurrency.swift"; sourceTree = ""; }; + 2C90829A29957A9B003FEB23 /* Extension-ExpenseStyling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension-ExpenseStyling.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,6 +66,7 @@ 2C145CAF298BF29700813CBC /* iExpenseApp.swift */, 2C145CB1298BF29700813CBC /* ContentView.swift */, 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */, + 2C90829A29957A9B003FEB23 /* Extension-ExpenseStyling.swift */, 2C6389F72995458D00210EA9 /* HeaderFont.swift */, 2C51729D2994114800461260 /* AddView.swift */, 2C5172992993FA4F00461260 /* ExpenseItem.swift */, @@ -157,6 +160,7 @@ 2C51729A2993FA4F00461260 /* ExpenseItem.swift in Sources */, 2C6389F82995458D00210EA9 /* HeaderFont.swift in Sources */, 2C51729E2994114800461260 /* AddView.swift in Sources */, + 2C90829B29957A9B003FEB23 /* Extension-ExpenseStyling.swift in Sources */, 2C145CB0298BF29700813CBC /* iExpenseApp.swift in Sources */, 2C9082992995792C003FEB23 /* Extension-LocalCurrency.swift in Sources */, ); diff --git a/10-iExpense/iExpense/iExpense/ContentView.swift b/10-iExpense/iExpense/iExpense/ContentView.swift index c881746..d52cfaa 100644 --- a/10-iExpense/iExpense/iExpense/ContentView.swift +++ b/10-iExpense/iExpense/iExpense/ContentView.swift @@ -29,7 +29,7 @@ struct ContentView: View { Spacer() Text(item.amount, format: .localCurrency) // challenge 1 - .foregroundColor(item.amount <= 10 ? .green : item.amount <= 100 ? .yellow : .red) // challenge 2 + .style(for: item) // challenge 2 } } .onDelete { IndexSet in diff --git a/10-iExpense/iExpense/iExpense/Extension-ExpenseStyling.swift b/10-iExpense/iExpense/iExpense/Extension-ExpenseStyling.swift new file mode 100644 index 0000000..d1a4298 --- /dev/null +++ b/10-iExpense/iExpense/iExpense/Extension-ExpenseStyling.swift @@ -0,0 +1,17 @@ +// Date: 2/10/23 +// +// Author: Zai Santillan +// Github: @plskz + + +import SwiftUI + +extension View { + /// challenge 2 - Modify the expense amounts in ContentView to contain some styling depending on their value + /// – expenses under $10 should have one style, + /// – expenses under $100 another + /// - expenses over $100 a third style. + func style(for item: ExpenseItem) -> some View { + item.amount <= 10 ? self.foregroundColor(.green) : item.amount <= 100 ? self.foregroundColor(.yellow) : self.foregroundColor(.red) + } +} From cc179b1e2e72bb3bf8df0235f6598aa99ce83ed3 Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 03:13:11 +0800 Subject: [PATCH 06/12] feat(iExpense): add `model` and `extensions` folder --- .../iExpense.xcodeproj/project.pbxproj | 24 +++++++++++++++---- .../Extension-ExpenseStyling.swift | 0 .../Extension-LocalCurrency.swift | 0 .../iExpense/{ => Model}/ExpenseItem.swift | 0 .../iExpense/{ => Model}/Expenses.swift | 0 5 files changed, 20 insertions(+), 4 deletions(-) rename 10-iExpense/iExpense/iExpense/{ => Extensions}/Extension-ExpenseStyling.swift (100%) rename 10-iExpense/iExpense/iExpense/{ => Extensions}/Extension-LocalCurrency.swift (100%) rename 10-iExpense/iExpense/iExpense/{ => Model}/ExpenseItem.swift (100%) rename 10-iExpense/iExpense/iExpense/{ => Model}/Expenses.swift (100%) diff --git a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj index bf178ef..9ff6861 100644 --- a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj +++ b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj @@ -65,12 +65,10 @@ children = ( 2C145CAF298BF29700813CBC /* iExpenseApp.swift */, 2C145CB1298BF29700813CBC /* ContentView.swift */, - 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */, - 2C90829A29957A9B003FEB23 /* Extension-ExpenseStyling.swift */, 2C6389F72995458D00210EA9 /* HeaderFont.swift */, 2C51729D2994114800461260 /* AddView.swift */, - 2C5172992993FA4F00461260 /* ExpenseItem.swift */, - 2C51729B2993FAB600461260 /* Expenses.swift */, + 2C90829C29957CA3003FEB23 /* Extensions */, + 2C90829D29957CE1003FEB23 /* Model */, 2C145CB3298BF29800813CBC /* Assets.xcassets */, 2C145CB5298BF29800813CBC /* Preview Content */, ); @@ -85,6 +83,24 @@ path = "Preview Content"; sourceTree = ""; }; + 2C90829C29957CA3003FEB23 /* Extensions */ = { + isa = PBXGroup; + children = ( + 2C90829A29957A9B003FEB23 /* Extension-ExpenseStyling.swift */, + 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 2C90829D29957CE1003FEB23 /* Model */ = { + isa = PBXGroup; + children = ( + 2C5172992993FA4F00461260 /* ExpenseItem.swift */, + 2C51729B2993FAB600461260 /* Expenses.swift */, + ); + path = Model; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ diff --git a/10-iExpense/iExpense/iExpense/Extension-ExpenseStyling.swift b/10-iExpense/iExpense/iExpense/Extensions/Extension-ExpenseStyling.swift similarity index 100% rename from 10-iExpense/iExpense/iExpense/Extension-ExpenseStyling.swift rename to 10-iExpense/iExpense/iExpense/Extensions/Extension-ExpenseStyling.swift diff --git a/10-iExpense/iExpense/iExpense/Extension-LocalCurrency.swift b/10-iExpense/iExpense/iExpense/Extensions/Extension-LocalCurrency.swift similarity index 100% rename from 10-iExpense/iExpense/iExpense/Extension-LocalCurrency.swift rename to 10-iExpense/iExpense/iExpense/Extensions/Extension-LocalCurrency.swift diff --git a/10-iExpense/iExpense/iExpense/ExpenseItem.swift b/10-iExpense/iExpense/iExpense/Model/ExpenseItem.swift similarity index 100% rename from 10-iExpense/iExpense/iExpense/ExpenseItem.swift rename to 10-iExpense/iExpense/iExpense/Model/ExpenseItem.swift diff --git a/10-iExpense/iExpense/iExpense/Expenses.swift b/10-iExpense/iExpense/iExpense/Model/Expenses.swift similarity index 100% rename from 10-iExpense/iExpense/iExpense/Expenses.swift rename to 10-iExpense/iExpense/iExpense/Model/Expenses.swift From aa1b95f19b54103e92d83447a3a718c8b44f98bf Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 05:02:04 +0800 Subject: [PATCH 07/12] chore(iExpense): improve challenge 3 also added EmptyList, ExpenseSection, ButtonStyle, fixes AddView when name is empty --- .../iExpense.xcodeproj/project.pbxproj | 12 +++ 10-iExpense/iExpense/iExpense/AddView.swift | 4 + .../iExpense/iExpense/ContentView.swift | 79 ++++++++++--------- 10-iExpense/iExpense/iExpense/EmptyList.swift | 37 +++++++++ .../iExpense/iExpense/ExpenseSection.swift | 46 +++++++++++ .../Extensions/Extension-ButtonStyle.swift | 28 +++++++ .../iExpense/iExpense/Model/ExpenseItem.swift | 2 +- .../iExpense/iExpense/Model/Expenses.swift | 12 +++ 8 files changed, 180 insertions(+), 40 deletions(-) create mode 100644 10-iExpense/iExpense/iExpense/EmptyList.swift create mode 100644 10-iExpense/iExpense/iExpense/ExpenseSection.swift create mode 100644 10-iExpense/iExpense/iExpense/Extensions/Extension-ButtonStyle.swift diff --git a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj index 9ff6861..28f9da0 100644 --- a/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj +++ b/10-iExpense/iExpense/iExpense.xcodeproj/project.pbxproj @@ -17,6 +17,9 @@ 2C6389F82995458D00210EA9 /* HeaderFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6389F72995458D00210EA9 /* HeaderFont.swift */; }; 2C9082992995792C003FEB23 /* Extension-LocalCurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */; }; 2C90829B29957A9B003FEB23 /* Extension-ExpenseStyling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C90829A29957A9B003FEB23 /* Extension-ExpenseStyling.swift */; }; + 2C90829F29958298003FEB23 /* ExpenseSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C90829E29958298003FEB23 /* ExpenseSection.swift */; }; + 2C9082A129958F90003FEB23 /* Extension-ButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9082A029958F90003FEB23 /* Extension-ButtonStyle.swift */; }; + 2C9082A32995900A003FEB23 /* EmptyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9082A22995900A003FEB23 /* EmptyList.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -31,6 +34,9 @@ 2C6389F72995458D00210EA9 /* HeaderFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderFont.swift; sourceTree = ""; }; 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension-LocalCurrency.swift"; sourceTree = ""; }; 2C90829A29957A9B003FEB23 /* Extension-ExpenseStyling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension-ExpenseStyling.swift"; sourceTree = ""; }; + 2C90829E29958298003FEB23 /* ExpenseSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpenseSection.swift; sourceTree = ""; }; + 2C9082A029958F90003FEB23 /* Extension-ButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension-ButtonStyle.swift"; sourceTree = ""; }; + 2C9082A22995900A003FEB23 /* EmptyList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyList.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -65,6 +71,8 @@ children = ( 2C145CAF298BF29700813CBC /* iExpenseApp.swift */, 2C145CB1298BF29700813CBC /* ContentView.swift */, + 2C9082A22995900A003FEB23 /* EmptyList.swift */, + 2C90829E29958298003FEB23 /* ExpenseSection.swift */, 2C6389F72995458D00210EA9 /* HeaderFont.swift */, 2C51729D2994114800461260 /* AddView.swift */, 2C90829C29957CA3003FEB23 /* Extensions */, @@ -86,6 +94,7 @@ 2C90829C29957CA3003FEB23 /* Extensions */ = { isa = PBXGroup; children = ( + 2C9082A029958F90003FEB23 /* Extension-ButtonStyle.swift */, 2C90829A29957A9B003FEB23 /* Extension-ExpenseStyling.swift */, 2C9082982995792C003FEB23 /* Extension-LocalCurrency.swift */, ); @@ -172,11 +181,14 @@ buildActionMask = 2147483647; files = ( 2C51729C2993FAB600461260 /* Expenses.swift in Sources */, + 2C90829F29958298003FEB23 /* ExpenseSection.swift in Sources */, 2C145CB2298BF29700813CBC /* ContentView.swift in Sources */, 2C51729A2993FA4F00461260 /* ExpenseItem.swift in Sources */, 2C6389F82995458D00210EA9 /* HeaderFont.swift in Sources */, 2C51729E2994114800461260 /* AddView.swift in Sources */, 2C90829B29957A9B003FEB23 /* Extension-ExpenseStyling.swift in Sources */, + 2C9082A129958F90003FEB23 /* Extension-ButtonStyle.swift in Sources */, + 2C9082A32995900A003FEB23 /* EmptyList.swift in Sources */, 2C145CB0298BF29700813CBC /* iExpenseApp.swift in Sources */, 2C9082992995792C003FEB23 /* Extension-LocalCurrency.swift in Sources */, ); diff --git a/10-iExpense/iExpense/iExpense/AddView.swift b/10-iExpense/iExpense/iExpense/AddView.swift index 1ea4770..2de211a 100644 --- a/10-iExpense/iExpense/iExpense/AddView.swift +++ b/10-iExpense/iExpense/iExpense/AddView.swift @@ -33,6 +33,10 @@ struct AddView: View { .navigationTitle("Add new expense") .toolbar { Button("Save") { + if name.isEmpty { + name = "Unknown" + } + let item = ExpenseItem(name: name, type: type, amount: amount) expenses.items.append(item) dismiss() diff --git a/10-iExpense/iExpense/iExpense/ContentView.swift b/10-iExpense/iExpense/iExpense/ContentView.swift index d52cfaa..4c86ec6 100644 --- a/10-iExpense/iExpense/iExpense/ContentView.swift +++ b/10-iExpense/iExpense/iExpense/ContentView.swift @@ -10,44 +10,36 @@ struct ContentView: View { @StateObject var expenses = Expenses() @State private var showingAddExpense = false - let types = ["Business", "Personal"] - var body: some View { NavigationView { List { - ForEach(types, id: \.self) { type in - Section { - // challenge 3 - ForEach(expensesFilter(type)) { item in - HStack { - VStack(alignment: .leading) { - Text(item.name) - .font(.headline) - Text(item.type) - } - - Spacer() - - Text(item.amount, format: .localCurrency) // challenge 1 - .style(for: item) // challenge 2 - } - } - .onDelete { IndexSet in - removeItems(at: IndexSet, for: type) - } - } header: { - if expensesFilter(type).count != 0 { - HeaderFont(type) - } - } + // challenge 3 + ExpenseSection(title: "Business", expenses: expenses.businessItems, deleteItems: removeBusinessItems) + // challenge 3 + ExpenseSection(title: "Personal", expenses: expenses.personalItems, deleteItems: removePersonalItems) + + if expenses.isEmpty { + EmptyList() } } .navigationTitle("iExpense 💸") .toolbar { - Button { - showingAddExpense = true - } label: { - Image(systemName: "plus") + ToolbarItem(placement: .primaryAction) { + Button { + showingAddExpense = true + } label: { + Image(systemName: "plus") + } + } + + if expenses.isEmpty { + // only show this when empty + ToolbarItem(placement: .bottomBar) { + Button("Add Expenses") { + showingAddExpense = true + } + .buttonStyle(.ghost) + } } } .sheet(isPresented: $showingAddExpense) { @@ -56,18 +48,27 @@ struct ContentView: View { } } - // challenge 3 - func expensesFilter(_ type: String) -> [ExpenseItem] { - expenses.items.filter { $0.type == type } + func removeItems(at offsets: IndexSet, in inputArray: [ExpenseItem]) { + var objectsToDelete = IndexSet() + + for offset in offsets { + let item = inputArray[offset] + + if let index = expenses.items.firstIndex(of: item) { + objectsToDelete.insert(index) + } + } + + expenses.items.remove(atOffsets: objectsToDelete) } - func removeItems(at offsets: IndexSet, for type: String) { - let chosenElement = expensesFilter(type)[offsets.first!] - let uuid = chosenElement.id - let index = expenses.items.firstIndex(where: { $0.id == uuid })! - expenses.items.remove(at: index) + func removePersonalItems(at offsets: IndexSet) { + removeItems(at: offsets, in: expenses.personalItems) } + func removeBusinessItems(at offsets: IndexSet) { + removeItems(at: offsets, in: expenses.businessItems) + } } struct ContentView_Previews: PreviewProvider { diff --git a/10-iExpense/iExpense/iExpense/EmptyList.swift b/10-iExpense/iExpense/iExpense/EmptyList.swift new file mode 100644 index 0000000..bcf9c7a --- /dev/null +++ b/10-iExpense/iExpense/iExpense/EmptyList.swift @@ -0,0 +1,37 @@ +// Date: 2/10/23 +// +// Author: Zai Santillan +// Github: @plskz + + +import SwiftUI + +struct EmptyList: View { + var body: some View { + VStack { + Image(systemName: "cart") + .font(.system(size: 128)) + + Text("Your Expenses is Empty!") + .font(.system(size: 22, weight: .heavy, design: .rounded)) + .padding() + + Text("Tap the button to add items") + .font(.system(size: 16, weight: .medium, design: .rounded)) + .foregroundColor(.secondary) + + Image(systemName: "arrow.down") + .font(.system(size: 64, weight: .ultraLight, design: .rounded)) + .padding(.top, 80) + } + .listRowBackground(Color.clear) + } +} + +struct EmptyList_Previews: PreviewProvider { + static var previews: some View { + List { + EmptyList() + } + } +} diff --git a/10-iExpense/iExpense/iExpense/ExpenseSection.swift b/10-iExpense/iExpense/iExpense/ExpenseSection.swift new file mode 100644 index 0000000..4be1027 --- /dev/null +++ b/10-iExpense/iExpense/iExpense/ExpenseSection.swift @@ -0,0 +1,46 @@ +// Date: 2/10/23 +// +// Author: Zai Santillan +// Github: @plskz + + +import SwiftUI + +/// For a bigger challenge, try splitting the expenses list into two sections: one for personal expenses, and one for business expenses. +/// This is tricky for a few reasons, not least because it means being careful about how items are deleted! +struct ExpenseSection: View { + var title: String + var expenses: [ExpenseItem] + var deleteItems: (IndexSet) -> Void + + var body: some View { + Section { + ForEach(expenses) { item in + HStack { + VStack(alignment: .leading) { + Text(item.name) + .font(.headline) + Text(item.type) + } + + Spacer() + + Text(item.amount, format: .localCurrency) // challenge 1 + .style(for: item) // challenge 2 + } + } + .onDelete(perform: deleteItems) + } header: { + if expenses.count != 0 { + HeaderFont(title) + } + } + } +} + +struct ExpenseSection_Previews: PreviewProvider { + static var previews: some View { + ExpenseSection(title: "Example", expenses: []) { _ in } + } +} + diff --git a/10-iExpense/iExpense/iExpense/Extensions/Extension-ButtonStyle.swift b/10-iExpense/iExpense/iExpense/Extensions/Extension-ButtonStyle.swift new file mode 100644 index 0000000..5304487 --- /dev/null +++ b/10-iExpense/iExpense/iExpense/Extensions/Extension-ButtonStyle.swift @@ -0,0 +1,28 @@ +// Date: 2/10/23 +// +// Author: Zai Santillan +// Github: @plskz + + +import SwiftUI + +struct GhostButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding() + .foregroundStyle(.tint) + .background( + RoundedRectangle( + cornerRadius: 20, + style: .continuous + ) + .stroke(.tint, lineWidth: 2) + ) + } +} + +extension ButtonStyle where Self == GhostButtonStyle { + static var ghost: Self { + return .init() + } +} diff --git a/10-iExpense/iExpense/iExpense/Model/ExpenseItem.swift b/10-iExpense/iExpense/iExpense/Model/ExpenseItem.swift index 46367fb..5eba435 100644 --- a/10-iExpense/iExpense/iExpense/Model/ExpenseItem.swift +++ b/10-iExpense/iExpense/iExpense/Model/ExpenseItem.swift @@ -6,7 +6,7 @@ import Foundation -struct ExpenseItem: Identifiable, Codable { +struct ExpenseItem: Identifiable, Codable, Equatable { var id = UUID() let name: String let type: String diff --git a/10-iExpense/iExpense/iExpense/Model/Expenses.swift b/10-iExpense/iExpense/iExpense/Model/Expenses.swift index f7a05bb..9a3dd3f 100644 --- a/10-iExpense/iExpense/iExpense/Model/Expenses.swift +++ b/10-iExpense/iExpense/iExpense/Model/Expenses.swift @@ -15,6 +15,18 @@ class Expenses: ObservableObject { } } + var personalItems: [ExpenseItem] { + items.filter { $0.type == "Personal" } + } + + var businessItems: [ExpenseItem] { + items.filter { $0.type == "Business" } + } + + var isEmpty: Bool { + businessItems.count == 0 && personalItems.count == 0 + } + init() { if let savedItems = UserDefaults.standard.data(forKey: "Items") { if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) { From 27996eaa879130864245b101e69687153be6ab37 Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 21:17:21 +0800 Subject: [PATCH 08/12] chore(iExpense): delete File.swift --- 10-iExpense/iExpense/iExpense/File.swift | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 10-iExpense/iExpense/iExpense/File.swift diff --git a/10-iExpense/iExpense/iExpense/File.swift b/10-iExpense/iExpense/iExpense/File.swift deleted file mode 100644 index d31bed2..0000000 --- a/10-iExpense/iExpense/iExpense/File.swift +++ /dev/null @@ -1,7 +0,0 @@ -// Date: 2/10/23 -// -// Author: Zai Santillan -// Github: @plskz - - -import Foundation From cc9ce130557435b8c5bb3b910b8bb16b7cda25dd Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 23:50:05 +0800 Subject: [PATCH 09/12] feat(iExpense): add example previews for ExpenseSection --- .../iExpense/iExpense/ExpenseSection.swift | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/10-iExpense/iExpense/iExpense/ExpenseSection.swift b/10-iExpense/iExpense/iExpense/ExpenseSection.swift index 4be1027..1d42429 100644 --- a/10-iExpense/iExpense/iExpense/ExpenseSection.swift +++ b/10-iExpense/iExpense/iExpense/ExpenseSection.swift @@ -40,7 +40,22 @@ struct ExpenseSection: View { struct ExpenseSection_Previews: PreviewProvider { static var previews: some View { - ExpenseSection(title: "Example", expenses: []) { _ in } + List { + ExpenseSection( + title: "Business", + expenses: [ + ExpenseItem(name: "Macbook Pro", type: "Business", amount: 1200), + ExpenseItem(name: "Uber", type: "Business", amount: 21.59) + ]) { _ in } + + ExpenseSection( + title: "Personal", + expenses: [ + ExpenseItem(name: "Breakfast", type: "Personal", amount: 10), + ExpenseItem(name: "Lunch", type: "Personal", amount: 12), + ExpenseItem(name: "Gift", type: "Personal", amount: 500) + ]) { _ in } + } } } From 6c20b72173c100704f95d93f22afedd88e175975 Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 23:50:15 +0800 Subject: [PATCH 10/12] refactor(iExpense): change removeItems parameter name --- 10-iExpense/iExpense/iExpense/ContentView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/10-iExpense/iExpense/iExpense/ContentView.swift b/10-iExpense/iExpense/iExpense/ContentView.swift index 4c86ec6..00bbcd9 100644 --- a/10-iExpense/iExpense/iExpense/ContentView.swift +++ b/10-iExpense/iExpense/iExpense/ContentView.swift @@ -33,7 +33,7 @@ struct ContentView: View { } if expenses.isEmpty { - // only show this when empty + // only show this when list is empty ToolbarItem(placement: .bottomBar) { Button("Add Expenses") { showingAddExpense = true @@ -48,7 +48,7 @@ struct ContentView: View { } } - func removeItems(at offsets: IndexSet, in inputArray: [ExpenseItem]) { + func removeItems(at offsets: IndexSet, from inputArray: [ExpenseItem]) { var objectsToDelete = IndexSet() for offset in offsets { @@ -63,11 +63,11 @@ struct ContentView: View { } func removePersonalItems(at offsets: IndexSet) { - removeItems(at: offsets, in: expenses.personalItems) + removeItems(at: offsets, from: expenses.personalItems) } func removeBusinessItems(at offsets: IndexSet) { - removeItems(at: offsets, in: expenses.businessItems) + removeItems(at: offsets, from: expenses.businessItems) } } From d5686d8cd4e340830a8410d10164124edff4e060 Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 23:54:00 +0800 Subject: [PATCH 11/12] chore(iExpense): add comments --- 10-iExpense/iExpense/iExpense/ExpenseSection.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/10-iExpense/iExpense/iExpense/ExpenseSection.swift b/10-iExpense/iExpense/iExpense/ExpenseSection.swift index 1d42429..f9f5869 100644 --- a/10-iExpense/iExpense/iExpense/ExpenseSection.swift +++ b/10-iExpense/iExpense/iExpense/ExpenseSection.swift @@ -6,7 +6,7 @@ import SwiftUI -/// For a bigger challenge, try splitting the expenses list into two sections: one for personal expenses, and one for business expenses. +/// challenge 3 - For a bigger challenge, try splitting the expenses list into two sections: one for personal expenses, and one for business expenses. /// This is tricky for a few reasons, not least because it means being careful about how items are deleted! struct ExpenseSection: View { var title: String From d91687f86e36751bc93b8412bb922e750f6ae998 Mon Sep 17 00:00:00 2001 From: Zai Santillan Date: Fri, 10 Feb 2023 23:59:28 +0800 Subject: [PATCH 12/12] feat(iExpense): disable autocorrection from AddView --- 10-iExpense/iExpense/iExpense/AddView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/10-iExpense/iExpense/iExpense/AddView.swift b/10-iExpense/iExpense/iExpense/AddView.swift index 2de211a..b1aa958 100644 --- a/10-iExpense/iExpense/iExpense/AddView.swift +++ b/10-iExpense/iExpense/iExpense/AddView.swift @@ -20,6 +20,7 @@ struct AddView: View { NavigationView { Form { TextField("Name", text: $name) + .autocorrectionDisabled(true) Picker("Type", selection: $type) { ForEach(types, id: \.self) {