Add Tab Groups

This commit is contained in:
2024-06-30 23:55:32 +02:00
parent 9dffb32382
commit 4d825f7d91
7 changed files with 223 additions and 94 deletions

View File

@@ -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 = "<group>"; };
D40CCAEE2C2DC5D8007C4A9F /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = "<group>"; };
D40CCAF22C2EE304007C4A9F /* AddSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSubscriptionView.swift; sourceTree = "<group>"; };
D426C5592C2F0F150057455D /* PaymentCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentCalendarView.swift; sourceTree = "<group>"; };
D4544FEE2C320AF30090E311 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
@@ -75,6 +78,17 @@
path = "Preview Content";
sourceTree = "<group>";
};
D4544FF02C320B920090E311 /* views */ = {
isa = PBXGroup;
children = (
D40CCAE12C2DC5A9007C4A9F /* ContentView.swift */,
D4544FEE2C320AF30090E311 /* HomeView.swift */,
D40CCAF22C2EE304007C4A9F /* AddSubscriptionView.swift */,
D426C5592C2F0F150057455D /* PaymentCalendarView.swift */,
);
path = views;
sourceTree = "<group>";
};
/* 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;

View File

@@ -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()
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}