From 3b6293f6e5596dcb2596d4409b3b75c905ea3644 Mon Sep 17 00:00:00 2001 From: k3y0708 Date: Mon, 1 Jul 2024 01:00:24 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20First=20draw=20of=20colored=20da?= =?UTF-8?q?ys=20in=20calendar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AboTracker/Subscription.swift | 35 ++++++ AboTracker/views/ContentView.swift | 23 +++- AboTracker/views/HomeView.swift | 32 ++---- AboTracker/views/PaymentCalendarView.swift | 117 ++++++++++++++++++++- 4 files changed, 179 insertions(+), 28 deletions(-) diff --git a/AboTracker/Subscription.swift b/AboTracker/Subscription.swift index cfc9956..266c442 100644 --- a/AboTracker/Subscription.swift +++ b/AboTracker/Subscription.swift @@ -57,6 +57,41 @@ final class Subscription: Identifiable { } return remaining } + + func getPaymentDaysIn(year: Int, month: Int) -> [Int] { + var paymentDays: [Int] = [] + + let calendar = Calendar.current + let components = DateComponents(year: year, month: month) + guard let date = calendar.date(from: components) else { + return paymentDays + } + + for payment in payments { + var currentDate = payment.startDate + while calendar.isDate(currentDate, equalTo: date, toGranularity: .month) { + let day = calendar.component(.day, from: currentDate) + paymentDays.append(day) + + switch payment.intervall { + case .weekly: + guard let nextDate = calendar.date(byAdding: .day, value: 7, to: currentDate) else { break } + currentDate = nextDate + case .monthly: + guard let nextDate = calendar.date(byAdding: .month, value: 1, to: currentDate) else { break } + currentDate = nextDate + case .quarter: + guard let nextDate = calendar.date(byAdding: .month, value: 3, to: currentDate) else { break } + currentDate = nextDate + case .yearly: + guard let nextDate = calendar.date(byAdding: .year, value: 1, to: currentDate) else { break } + currentDate = nextDate + } + } + } + + return paymentDays + } } final class Payment: Identifiable { diff --git a/AboTracker/views/ContentView.swift b/AboTracker/views/ContentView.swift index 5a37cfc..3f77bb7 100644 --- a/AboTracker/views/ContentView.swift +++ b/AboTracker/views/ContentView.swift @@ -1,20 +1,39 @@ import SwiftUI struct ContentView: View { + @State private var subs: [Subscription] = [] + + init() { + self._subs = State(initialValue: [ + Subscription(name: "Test", payments: [Payment(amount: 9.99, intervall: .monthly, startDate: getDate(from: "2023-01-01"))], color: .blue), + Subscription(name: "Fitness First", payments: [ + Payment(amount: 7.9, intervall: .weekly, startDate: getDate(from: "2023-01-23")), + Payment(amount: 29, intervall: .quarter, startDate: getDate(from: "2023-04-03")) + ], color: .red) + ]) + } + var body: some View { TabView { - HomeView() + HomeView(subs: $subs) .tabItem { Image(systemName: "house.fill") Text("Home") } - PaymentCalendarView() + PaymentCalendarView(subs: $subs) .tabItem { Image(systemName: "calendar") Text("Calendar") } } + .background(Color.gray.opacity(0.2)) + } + + private func getDate(from dateString: String) -> Date { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + return formatter.date(from: dateString) ?? Date() } } diff --git a/AboTracker/views/HomeView.swift b/AboTracker/views/HomeView.swift index 2f92168..63a9a22 100644 --- a/AboTracker/views/HomeView.swift +++ b/AboTracker/views/HomeView.swift @@ -1,18 +1,8 @@ import SwiftUI struct HomeView: View { + @Binding var subs: [Subscription] @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 { @@ -105,22 +95,16 @@ struct HomeView: View { } 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() + HomeView(subs: .constant([ + Subscription(name: "Test", payments: [Payment(amount: 9.99, intervall: .monthly, startDate: Date())], color: .blue), + Subscription(name: "Fitness First", payments: [ + Payment(amount: 7.9, intervall: .weekly, startDate: Date()), + Payment(amount: 29, intervall: .quarter, startDate: Date()) + ], color: .red) + ])) } } diff --git a/AboTracker/views/PaymentCalendarView.swift b/AboTracker/views/PaymentCalendarView.swift index 15e1221..6799db4 100644 --- a/AboTracker/views/PaymentCalendarView.swift +++ b/AboTracker/views/PaymentCalendarView.swift @@ -1,18 +1,131 @@ import SwiftUI struct PaymentCalendarView: View { + @Binding var subs: [Subscription] + let calendar = Calendar.current + let dateFormatter = DateFormatter() + + @State private var currentDate = Date() + var body: some View { VStack { Text("Payment Calendar") .font(.title) .padding() - Spacer() + + Text(currentMonthYear()) + .font(.headline) + .padding() + + calendarView() } + .padding() + } + + func currentMonthYear() -> String { + let formatter = DateFormatter() + formatter.dateFormat = "MMMM yyyy" + return formatter.string(from: currentDate) + } + + func calendarView() -> some View { + let days = daysInMonth(date: currentDate) + let firstDay = firstDayOfMonth(date: currentDate) + + let columns = Array(repeating: GridItem(.flexible()), count: 7) + let weeks = (days.count + firstDay) / 7 + ((days.count + firstDay) % 7 == 0 ? 0 : 1) + + return LazyVGrid(columns: columns, spacing: 20) { + ForEach(0..<7, id: \.self) { index in + Text(daySymbol(index: index)) + .fontWeight(.bold) + } + + ForEach(0..<(weeks * 7), id: \.self) { index in + if index < firstDay || index - firstDay >= days.count { + Text("") + } else { + let day = index - firstDay + 1 + let dayDate = calendar.date(byAdding: .day, value: day - 1, to: startOfMonth(date: currentDate))! + let paymentColor = getPaymentColor(for: dayDate) + + Text("\(day)") + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(paymentColor) + .cornerRadius(8) + } + } + } + } + + func getPaymentColor(for date: Date) -> Color { + for sub in subs { + for payment in sub.payments { + if isPaymentDue(payment: payment, for: date) { + return sub.color.opacity(0.3) + } + } + } + return Color.clear + } + + func isPaymentDue(payment: Payment, for date: Date) -> Bool { + switch payment.intervall { + case .weekly: + let startOfWeek = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: date))! + let endOfWeek = calendar.date(byAdding: .day, value: 6, to: startOfWeek)! + return date >= startOfWeek && date <= endOfWeek && calendar.component(.weekday, from: payment.startDate) == calendar.component(.weekday, from: date) + + case .monthly: + // ToDo: Payment not visible if Payment is at example at 31th of month, but month only has 30 days + return calendar.component(.day, from: payment.startDate) == calendar.component(.day, from: date) + + case .quarter: + guard let quarterStartDate = calendar.date(byAdding: .month, value: -3, to: payment.startDate) else { + return false + } + let startOfQuarter = calendar.date(from: calendar.dateComponents([.year, .month], from: quarterStartDate))! + let endOfQuarter = calendar.date(byAdding: .month, value: 3, to: startOfQuarter)! + return date >= startOfQuarter && date <= endOfQuarter + + case .yearly: + return calendar.component(.year, from: payment.startDate) == calendar.component(.year, from: date) + } + } + + func daysInMonth(date: Date) -> [Date] { + guard let range = calendar.range(of: .day, in: .month, for: date) else { return [] } + return range.compactMap { day -> Date? in + var components = calendar.dateComponents([.year, .month], from: date) + components.day = day + return calendar.date(from: components) + } + } + + func firstDayOfMonth(date: Date) -> Int { + let components = calendar.dateComponents([.year, .month], from: date) + guard let firstDay = calendar.date(from: components) else { return 0 } + return calendar.component(.weekday, from: firstDay) - calendar.firstWeekday + } + + func daySymbol(index: Int) -> String { + dateFormatter.shortWeekdaySymbols[(index + calendar.firstWeekday - 1) % 7] + } + + func startOfMonth(date: Date) -> Date { + let components = calendar.dateComponents([.year, .month], from: date) + return calendar.date(from: components) ?? date } } struct PaymentCalendarView_Previews: PreviewProvider { static var previews: some View { - PaymentCalendarView() + PaymentCalendarView(subs: .constant([ + Subscription(name: "Test", payments: [Payment(amount: 9.99, intervall: .monthly, startDate: Date())], color: .blue), + Subscription(name: "Fitness First", payments: [ + Payment(amount: 7.9, intervall: .weekly, startDate: Date()), + Payment(amount: 29, intervall: .quarter, startDate: Date()) + ], color: .red) + ])) } }