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