From 4d825f7d917d3b558efaef4dffd862c7db4d0c7a Mon Sep 17 00:00:00 2001 From: k3y0708 Date: Sun, 30 Jun 2024 23:55:32 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20Tab=20Groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AboTracker.xcodeproj/project.pbxproj | 22 ++- AboTracker/ContentView.swift | 88 ------------ AboTracker/Subscription.swift | 32 ++++- .../{ => views}/AddSubscriptionView.swift | 6 +- AboTracker/views/ContentView.swift | 25 ++++ AboTracker/views/HomeView.swift | 126 ++++++++++++++++++ AboTracker/views/PaymentCalendarView.swift | 18 +++ 7 files changed, 223 insertions(+), 94 deletions(-) delete mode 100644 AboTracker/ContentView.swift rename AboTracker/{ => views}/AddSubscriptionView.swift (92%) create mode 100644 AboTracker/views/ContentView.swift create mode 100644 AboTracker/views/HomeView.swift create mode 100644 AboTracker/views/PaymentCalendarView.swift diff --git a/AboTracker.xcodeproj/project.pbxproj b/AboTracker.xcodeproj/project.pbxproj index d07975e..08cf052 100644 --- a/AboTracker.xcodeproj/project.pbxproj +++ b/AboTracker.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ D40CCAE82C2DC5AA007C4A9F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D40CCAE72C2DC5AA007C4A9F /* Preview Assets.xcassets */; }; D40CCAEF2C2DC5D8007C4A9F /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40CCAEE2C2DC5D8007C4A9F /* Subscription.swift */; }; D40CCAF32C2EE305007C4A9F /* AddSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40CCAF22C2EE304007C4A9F /* AddSubscriptionView.swift */; }; + D426C55A2C2F0F150057455D /* PaymentCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D426C5592C2F0F150057455D /* PaymentCalendarView.swift */; }; + D4544FEF2C320AF30090E311 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4544FEE2C320AF30090E311 /* HomeView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -24,6 +26,8 @@ D40CCAE72C2DC5AA007C4A9F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; D40CCAEE2C2DC5D8007C4A9F /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; D40CCAF22C2EE304007C4A9F /* AddSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSubscriptionView.swift; sourceTree = ""; }; + D426C5592C2F0F150057455D /* PaymentCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentCalendarView.swift; sourceTree = ""; }; + D4544FEE2C320AF30090E311 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -57,12 +61,11 @@ isa = PBXGroup; children = ( D40CCADF2C2DC5A9007C4A9F /* AboTrackerApp.swift */, - D40CCAF22C2EE304007C4A9F /* AddSubscriptionView.swift */, - D40CCAE12C2DC5A9007C4A9F /* ContentView.swift */, + D40CCAEE2C2DC5D8007C4A9F /* Subscription.swift */, + D4544FF02C320B920090E311 /* views */, D40CCAE32C2DC5AA007C4A9F /* Assets.xcassets */, D40CCAE52C2DC5AA007C4A9F /* AboTracker.entitlements */, D40CCAE62C2DC5AA007C4A9F /* Preview Content */, - D40CCAEE2C2DC5D8007C4A9F /* Subscription.swift */, ); path = AboTracker; sourceTree = ""; @@ -75,6 +78,17 @@ path = "Preview Content"; sourceTree = ""; }; + D4544FF02C320B920090E311 /* views */ = { + isa = PBXGroup; + children = ( + D40CCAE12C2DC5A9007C4A9F /* ContentView.swift */, + D4544FEE2C320AF30090E311 /* HomeView.swift */, + D40CCAF22C2EE304007C4A9F /* AddSubscriptionView.swift */, + D426C5592C2F0F150057455D /* PaymentCalendarView.swift */, + ); + path = views; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -148,6 +162,8 @@ D40CCAEF2C2DC5D8007C4A9F /* Subscription.swift in Sources */, D40CCAF32C2EE305007C4A9F /* AddSubscriptionView.swift in Sources */, D40CCAE22C2DC5A9007C4A9F /* ContentView.swift in Sources */, + D4544FEF2C320AF30090E311 /* HomeView.swift in Sources */, + D426C55A2C2F0F150057455D /* PaymentCalendarView.swift in Sources */, D40CCAE02C2DC5A9007C4A9F /* AboTrackerApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/AboTracker/ContentView.swift b/AboTracker/ContentView.swift deleted file mode 100644 index b8335c2..0000000 --- a/AboTracker/ContentView.swift +++ /dev/null @@ -1,88 +0,0 @@ -import SwiftUI - -struct ContentView: View { - @State private var showAddSubscriptionSheet = false - @State private var subs: [Subscription] = [ - Subscription(name: "Test", payments: [Payment(amount: 9.99, intervall: .monthly)], color: .blue), - Subscription(name: "Fitness First", payments: [Payment(amount: 8, intervall: .weekly), Payment(amount: 30, intervall: .quarter)], color: .red) - ] - - var body: some View { - NavigationView { - List { - ForEach(subs) { sub in - Section { - VStack(alignment: .leading, spacing: 8) { - Text(sub.name) - .font(.headline) - ForEach(sub.payments) { payment in - HStack { - Text("\(payment.amount, specifier: "%.2f")\(Currency.euro.description)") - Spacer() - Text("/\(payment.intervall.description)") - .foregroundColor(.gray) - } - .font(.subheadline) - } - } - .padding(.vertical, 8) - } - .listRowBackground(sub.color.opacity(0.2)) - .cornerRadius(8) - } - .onDelete(perform: deleteSubscription) - - Section(header: Text("Totals")) { - HStack { - Text("Monthly Total") - Spacer() - Text("\(getMonthlyTotal(subs: subs), specifier: "%.2f")\(Currency.euro.description)") - .foregroundColor(.gray) - } - } - } - .navigationTitle("Subscriptions") - .toolbar { - #if os(iOS) - ToolbarItem(placement: .navigationBarLeading) { - EditButton() - } - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { - showAddSubscriptionSheet.toggle() - }) { - Image(systemName: "plus") - } - } - #elseif os(macOS) - ToolbarItem(placement: .primaryAction) { - Button(action: { - showAddSubscriptionSheet.toggle() - }) { - Image(systemName: "plus") - } - } - #endif - } - .sheet(isPresented: $showAddSubscriptionSheet) { - AddSubscriptionView(subs: $subs) - } - } - } - - func deleteSubscription(at offsets: IndexSet) { - subs.remove(atOffsets: offsets) - } - - func getMonthlyTotal(subs: [Subscription]) -> Float { - var monthlyTotal: Float = 0.0 - for sub in subs { - monthlyTotal += sub.getMonthlyAmount() - } - return monthlyTotal - } -} - -#Preview { - ContentView() -} diff --git a/AboTracker/Subscription.swift b/AboTracker/Subscription.swift index dbfe500..cfc9956 100644 --- a/AboTracker/Subscription.swift +++ b/AboTracker/Subscription.swift @@ -29,16 +29,46 @@ final class Subscription: Identifiable { } return sum } + + func getRemainingForCurrentMonth() -> Float { + var remaining: Float = 0.0 + let calendar = Calendar.current + let today = Date() + let range = calendar.range(of: .day, in: .month, for: today)! + let daysInMonth = range.count + + for payment in payments { + let components = calendar.dateComponents([.day], from: today, to: payment.startDate) + let daysRemaining = max(0, (components.day ?? 0) + 1) + + switch payment.intervall { + case .weekly: + let paymentsRemaining = Float(daysRemaining) / 7.0 + remaining += payment.amount * paymentsRemaining + case .monthly: + remaining += (daysRemaining >= daysInMonth) ? 0 : payment.amount + case .quarter: + let paymentsRemaining = Float(daysRemaining) / 90.0 + remaining += payment.amount * paymentsRemaining + case .yearly: + let paymentsRemaining = Float(daysRemaining) / 365.0 + remaining += payment.amount * paymentsRemaining + } + } + return remaining + } } final class Payment: Identifiable { public let id = UUID() var amount: Float var intervall: PaymentIntervall + var startDate: Date - init(amount: Float, intervall: PaymentIntervall) { + init(amount: Float, intervall: PaymentIntervall, startDate: Date) { self.amount = amount self.intervall = intervall + self.startDate = startDate } } diff --git a/AboTracker/AddSubscriptionView.swift b/AboTracker/views/AddSubscriptionView.swift similarity index 92% rename from AboTracker/AddSubscriptionView.swift rename to AboTracker/views/AddSubscriptionView.swift index c81c4f0..f64a1b1 100644 --- a/AboTracker/AddSubscriptionView.swift +++ b/AboTracker/views/AddSubscriptionView.swift @@ -5,7 +5,7 @@ struct AddSubscriptionView: View { @Binding var subs: [Subscription] @State private var name: String = "" - @State private var payments: [Payment] = [Payment(amount: 0, intervall: .monthly)] + @State private var payments: [Payment] = [Payment(amount: 0, intervall: .monthly, startDate: Date())] @State private var color: Color = .blue var body: some View { @@ -33,13 +33,15 @@ struct AddSubscriptionView: View { Text("Yearly").tag(PaymentIntervall.yearly) } .pickerStyle(SegmentedPickerStyle()) + + DatePicker("Start Date", selection: $payment.startDate, displayedComponents: .date) } } .onDelete(perform: deletePayment) Section { Button("Add Payment") { - let newPayment = Payment(amount: 0, intervall: .monthly) + let newPayment = Payment(amount: 0, intervall: .monthly, startDate: Date()) payments.append(newPayment) } } diff --git a/AboTracker/views/ContentView.swift b/AboTracker/views/ContentView.swift new file mode 100644 index 0000000..5a37cfc --- /dev/null +++ b/AboTracker/views/ContentView.swift @@ -0,0 +1,25 @@ +import SwiftUI + +struct ContentView: View { + var body: some View { + TabView { + HomeView() + .tabItem { + Image(systemName: "house.fill") + Text("Home") + } + + PaymentCalendarView() + .tabItem { + Image(systemName: "calendar") + Text("Calendar") + } + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/AboTracker/views/HomeView.swift b/AboTracker/views/HomeView.swift new file mode 100644 index 0000000..2f92168 --- /dev/null +++ b/AboTracker/views/HomeView.swift @@ -0,0 +1,126 @@ +import SwiftUI + +struct HomeView: View { + @State private var showAddSubscriptionSheet = false + @State private var subs: [Subscription] = [] + + init() { + self._subs = State(initialValue: [ + Subscription(name: "Test", payments: [Payment(amount: 9.99, intervall: .monthly, startDate: self.getDate(from: "2023-01-01"))], color: .blue), + Subscription(name: "Fitness First", payments: [ + Payment(amount: 7.9, intervall: .weekly, startDate: self.getDate(from: "2023-01-23")), + Payment(amount: 29, intervall: .quarter, startDate: self.getDate(from: "2023-04-03")) + ], color: .red) + ]) + } + + var body: some View { + NavigationView { + VStack { + List { + ForEach(subs) { sub in + Section { + VStack(alignment: .leading, spacing: 8) { + Text(sub.name) + .font(.headline) + ForEach(sub.payments) { payment in + HStack { + Text("\(payment.amount, specifier: "%.2f")\(Currency.euro.description)") + Spacer() + Text("/\(payment.intervall.description)") + .foregroundColor(.gray) + } + .font(.subheadline) + } + } + .padding(.vertical, 8) + } + .listRowBackground(sub.color.opacity(0.2)) + .cornerRadius(8) + } + .onDelete(perform: deleteSubscription) + + Section(header: Text("Totals")) { + HStack { + Text("Monthly Total") + Spacer() + Text("\(getMonthlyTotal(subs: subs), specifier: "%.2f")\(Currency.euro.description)") + .foregroundColor(.gray) + } + + HStack { + Text("Remaining for Current Month") + Spacer() + Text("\(getRemainingForCurrentMonth(subs: subs), specifier: "%.2f")\(Currency.euro.description)") + .foregroundColor(.gray) + } + } + } + .navigationTitle("Subscriptions") + .toolbar { + #if os(iOS) + ToolbarItem(placement: .navigationBarLeading) { + EditButton() + } + ToolbarItem(placement: .navigationBarTrailing) { + Button(action: { + showAddSubscriptionSheet.toggle() + }) { + Image(systemName: "plus") + } + } + #elseif os(macOS) + ToolbarItem(placement: .primaryAction) { + Button(action: { + showAddSubscriptionSheet.toggle() + }) { + Image(systemName: "plus") + } + } + #endif + } + .sheet(isPresented: $showAddSubscriptionSheet) { + AddSubscriptionView(subs: $subs) + } + } + } + } + + func deleteSubscription(at offsets: IndexSet) { + subs.remove(atOffsets: offsets) + } + + func getMonthlyTotal(subs: [Subscription]) -> Float { + var monthlyTotal: Float = 0.0 + for sub in subs { + monthlyTotal += sub.getMonthlyAmount() + } + return monthlyTotal + } + + func getRemainingForCurrentMonth(subs: [Subscription]) -> Float { + var remainingTotal: Float = 0.0 + for sub in subs { + remainingTotal += sub.getRemainingForCurrentMonth() + } + return remainingTotal + } + + func getDate(from dateString: String) -> Date { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + return formatter.date(from: dateString) ?? Date() + } + + func formattedDate(_ date: Date) -> String { + let formatter = DateFormatter() + formatter.dateStyle = .medium + return formatter.string(from: date) + } +} + +struct HomeView_Previews: PreviewProvider { + static var previews: some View { + HomeView() + } +} diff --git a/AboTracker/views/PaymentCalendarView.swift b/AboTracker/views/PaymentCalendarView.swift new file mode 100644 index 0000000..15e1221 --- /dev/null +++ b/AboTracker/views/PaymentCalendarView.swift @@ -0,0 +1,18 @@ +import SwiftUI + +struct PaymentCalendarView: View { + var body: some View { + VStack { + Text("Payment Calendar") + .font(.title) + .padding() + Spacer() + } + } +} + +struct PaymentCalendarView_Previews: PreviewProvider { + static var previews: some View { + PaymentCalendarView() + } +}