/ SpendingTracker / ContentView.swift
ContentView.swift
1 // 2 // ContentView.swift 3 // SpendingTracker 4 // 5 // Created by Joshua Söhn on 21.07.25. 6 // 7 8 import SwiftUI 9 import SwiftData 10 11 extension Color { 12 static let backgroundGray = Color(UIColor { traitCollection in 13 traitCollection.userInterfaceStyle == .dark 14 ? UIColor.black // or your custom dark color 15 : UIColor.systemGray5 16 }) 17 static let cardBackground = Color(UIColor { traitCollection in 18 traitCollection.userInterfaceStyle == .dark 19 ? UIColor.systemGray5 // or your custom dark color 20 : UIColor.white 21 }) 22 } 23 24 struct ContentView: View { 25 @Environment(\.modelContext) private var modelContext 26 @Query(sort: \Month.createdAt) private var months: [Month] 27 28 // Helper to parse all numbers from the text and sum them 29 private func total(for text: String) -> Double { 30 let pattern = #"(\d{1,3}(?:[.,]\d{2})?)\s*€?"# 31 let regex = try? NSRegularExpression(pattern: pattern, options: []) 32 let nsrange = NSRange(text.startIndex..<text.endIndex, in: text) 33 var sum: Double = 0.0 34 regex?.enumerateMatches(in: text, options: [], range: nsrange) { match, _, _ in 35 if let match = match, let range = Range(match.range(at: 1), in: text) { 36 var numberString = String(text[range]) 37 numberString = numberString.replacingOccurrences(of: ",", with: ".") 38 if let value = Double(numberString) { 39 sum += value 40 } 41 } 42 } 43 return sum 44 } 45 46 private func nextMonthTitle() -> String { 47 let formatter = DateFormatter() 48 formatter.locale = Locale(identifier: "de_DE") 49 formatter.dateFormat = "LLLL yyyy" 50 return formatter.string(from: Date()).capitalized 51 } 52 53 var body: some View { 54 ScrollView { 55 VStack(alignment: .leading, spacing: 16) { 56 ForEach(months) { month in 57 MonthCard( 58 month: month, 59 total: total(for: month.expensesText) 60 ) 61 } 62 if let last = months.last, total(for: last.expensesText) > 0 { 63 Button(action: { 64 let newMonth = Month(title: nextMonthTitle(), expensesText: "", createdAt: Date()) 65 modelContext.insert(newMonth) 66 }) { 67 HStack { 68 Image(systemName: "plus.circle.fill") 69 Text("New month") 70 .bold() 71 } 72 .font(.title3) 73 .padding() 74 .foregroundColor(.primary) 75 .frame(maxWidth: .infinity) 76 .background(Color(.systemGray6)) 77 .cornerRadius(20) 78 .padding(.horizontal, 4) 79 } 80 } 81 Spacer(minLength: 40) 82 } 83 .padding() 84 } 85 .background(Color.backgroundGray.ignoresSafeArea()) 86 .onAppear { 87 if months.isEmpty { 88 let formatter = DateFormatter() 89 formatter.locale = Locale(identifier: "de_DE") 90 formatter.dateFormat = "LLLL yyyy" 91 let defaultTitle = formatter.string(from: Date()).capitalized 92 let newMonth = Month(title: defaultTitle, expensesText: "", createdAt: Date()) 93 modelContext.insert(newMonth) 94 } 95 } 96 } 97 } 98 99 struct MonthCard: View { 100 var month: Month 101 let total: Double 102 103 var body: some View { 104 VStack(alignment: .leading, spacing: 16) { 105 TextField("Monatstitel", text: Binding( 106 get: { month.title }, 107 set: { month.title = $0 } 108 )) 109 .font(.largeTitle) 110 .bold() 111 .disableAutocorrection(true) 112 .autocapitalization(.words) 113 .padding(.vertical, 4) 114 .padding(.horizontal, 2) 115 .background(Color.clear) 116 .textFieldStyle(PlainTextFieldStyle()) 117 118 TextEditor(text: Binding( 119 get: { month.expensesText }, 120 set: { month.expensesText = $0 } 121 )) 122 .frame(minHeight: 100, maxHeight: 300) 123 .font(.system(size: 20, design: .default)) 124 .autocapitalization(.sentences) 125 .disableAutocorrection(true) 126 .scrollContentBackground(.hidden) 127 .background(Color.cardBackground) 128 129 Text(String(format: "%.2f€ total", total)) 130 .font(.title2) 131 .bold() 132 } 133 .padding(20) 134 .background(Color.cardBackground) 135 .cornerRadius(20) 136 .padding(.horizontal, 4) 137 } 138 } 139 140 #Preview { 141 // Create an in-memory model container for previews 142 let container = try! ModelContainer( 143 for: Month.self, 144 configurations: ModelConfiguration(isStoredInMemoryOnly: true) 145 ) 146 // Insert sample data 147 let context = container.mainContext 148 let sampleMonth = Month(title: "Juli 2025", expensesText: "Dinner 50,00€\nBücher 20,00€") 149 context.insert(sampleMonth) 150 return ContentView() 151 .modelContainer(container) 152 }