Last active
October 30, 2025 13:04
-
-
Save theoknock/af2b060a70a7bf1bb70d7651bdd2be37 to your computer and use it in GitHub Desktop.
A Swift/SwiftUI app that calculates planetary hour data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // TempSwiftUIView.swift | |
| // PlanetaryHours2025-Claude | |
| // | |
| // Created by Xcode Developer on 10/30/25. | |
| // | |
| import SwiftUI | |
| import WeatherKit | |
| import CoreLocation | |
| import Observation | |
| import Combine | |
| // MARK: - Main View | |
| struct ContentView: View { | |
| @State private var viewModel = PlanetaryHoursViewModel() | |
| @State private var locationManager = LocationManager() | |
| @State private var location: CLLocation = CLLocation() | |
| var body: some View { | |
| NavigationView { | |
| VStack(spacing: 0) { | |
| Text("Planetary Hour Calculator") | |
| .font(.title2) | |
| .foregroundStyle(Color(.label)) | |
| HStack { | |
| // Spacer() | |
| Text("JAMES ALAN BUSH") | |
| // .multilineTextAlignment(TextAlignment.leading) | |
| // .font(.caption2) | |
| // .foregroundStyle(Color(.tertiaryLabel)) | |
| // .scaledToFill() | |
| // Spacer() | |
| Text("VERSION 0.1") | |
| // .multilineTextAlignment(TextAlignment.center) | |
| // .font(.caption2) | |
| // .foregroundStyle(Color(.label)) | |
| // .scaledToFill() | |
| // Spacer() | |
| Text("BUILD 1") | |
| // .multilineTextAlignment(TextAlignment.trailing) | |
| // .font(.caption2) | |
| // .foregroundStyle(Color(.label)) | |
| // .scaledToFill() | |
| // Spacer() | |
| } | |
| .padding(.vertical) | |
| .foregroundStyle(.tertiary) | |
| .font(.caption2) | |
| .frame(maxWidth: .infinity) | |
| // Input Section | |
| Form { | |
| Section(header: Text("Location")) { | |
| HStack { | |
| VStack(alignment: .leading, spacing: 4) { | |
| Text("Current Location") | |
| .font(.subheadline) | |
| .foregroundColor(.secondary) | |
| if let location = locationManager.location { | |
| Text(viewModel.locationName) | |
| .font(.caption) | |
| .foregroundColor(.blue) | |
| Text("Lat: \(location.coordinate.latitude, specifier: "%.4f"), Lon: \(location.coordinate.longitude, specifier: "%.4f")") | |
| .font(.caption2) | |
| .foregroundColor(.gray) | |
| } else { | |
| Text("Location not available") | |
| .font(.caption) | |
| .foregroundColor(.gray) | |
| } | |
| } | |
| Spacer() | |
| Button(action: { | |
| locationManager.requestLocation() | |
| }) { | |
| Image(systemName: "location.fill") | |
| .foregroundColor(.blue) | |
| } | |
| .buttonStyle(.borderless) | |
| } | |
| if let error = locationManager.locationError { | |
| Text(error) | |
| .font(.caption) | |
| .foregroundColor(.red) | |
| } | |
| if locationManager.authorizationStatus == .denied || | |
| locationManager.authorizationStatus == .restricted { | |
| Button("Open Settings") { | |
| if let url = URL(string: UIApplication.openSettingsURLString) { | |
| UIApplication.shared.open(url) | |
| } | |
| } | |
| .font(.caption) | |
| } | |
| } | |
| Section(header: Text("Date")) { | |
| HStack(alignment: VerticalAlignment.center, spacing: 10.0, content: { | |
| DatePicker( | |
| "", | |
| selection: $viewModel.selectedDate, | |
| in: dateRange, // <-- This restricts the selectable dates | |
| displayedComponents: .date | |
| ) | |
| Button { | |
| guard let location = locationManager.location else { | |
| viewModel.errorMessage = "Please enable location services first" | |
| return | |
| } | |
| Task { | |
| await viewModel.calculatePlanetaryHours(for: location) | |
| } | |
| } label: { | |
| Text(viewModel.isLoading ? "Calculating..." : "Recalculate") | |
| .background(Color.blue.opacity(0.1)).cornerRadius(15) | |
| } | |
| }) | |
| } | |
| // Section { | |
| // Button(action: { | |
| // guard let location = locationManager.location else { | |
| // viewModel.errorMessage = "Please enable location services first" | |
| // return | |
| // } | |
| // Task { | |
| // await viewModel.calculatePlanetaryHours(for: location) | |
| // } | |
| // }) { | |
| // // HStack { | |
| // // VStack { | |
| // // Spacer() | |
| // if viewModel.isLoading { | |
| // ProgressView() | |
| // // .padding() | |
| // } | |
| // Text(viewModel.isLoading ? "Calculating..." : "Recalculate") | |
| // .background(Color.blue.opacity(0.1)).cornerRadius(15) | |
| // // Spacer() | |
| // // } | |
| // // } | |
| //// } | |
| // | |
| // | |
| // | |
| // // .border(.blue) | |
| // .disabled(viewModel.isLoading || locationManager.location == nil) | |
| // } | |
| // // .font(.caption) | |
| //// .padding(.horizontal) | |
| //// .multilineTextAlignment(TextAlignment.center) | |
| // | |
| // } | |
| } | |
| // DatePicker("Select Date", selection: $viewModel.selectedDate, displayedComponents: .date) | |
| } | |
| .background(Color.gray.opacity(0.1)) | |
| // Section { | |
| // Button(action: { | |
| // guard let location = locationManager.location else { | |
| // viewModel.errorMessage = "Please enable location services first" | |
| // return | |
| // } | |
| // Task { | |
| // await viewModel.calculatePlanetaryHours(for: location) | |
| // } | |
| // }) { | |
| // HStack { | |
| // Spacer() | |
| // if viewModel.isLoading { | |
| // ProgressView() | |
| // .padding(.trailing, 8) | |
| // } | |
| // Text(viewModel.isLoading ? "Calculating..." : "Calculate Planetary Hours") | |
| // Spacer() | |
| // } | |
| // } | |
| // .disabled(viewModel.isLoading || locationManager.location == nil) | |
| // } | |
| } | |
| .task { | |
| locationManager.requestLocation() | |
| if let location = locationManager.location { | |
| await viewModel.calculatePlanetaryHours(for: location) | |
| } | |
| } | |
| .onAppear { | |
| locationManager.requestLocation() | |
| } | |
| .onChange(of: viewModel.selectedDate) { oldValue, newValue in | |
| guard let location = locationManager.location else { return } | |
| Task { await viewModel.calculatePlanetaryHours(for: location) } | |
| } | |
| // Results Section | |
| if let error = viewModel.errorMessage { | |
| Text(error) | |
| .foregroundColor(.red) | |
| .padding() | |
| } | |
| if !viewModel.planetaryHours.isEmpty { | |
| PlanetaryHoursTableView(hours: viewModel.planetaryHours) | |
| // .frame(height: UIScreen.main.bounds.height | |
| } | |
| // Spacer() | |
| } | |
| } | |
| // .navigationTitle("Planetary Hours") | |
| // .task { | |
| // locationManager.requestLocation() | |
| // if let location = locationManager.location { | |
| // await viewModel.calculatePlanetaryHours(for: location) | |
| // } | |
| // } | |
| // .onAppear { | |
| // locationManager.requestLocation() | |
| // } | |
| // .onChange(of: viewModel.selectedDate) { oldValue, newValue in | |
| // guard let location = locationManager.location else { return } | |
| // Task { await viewModel.calculatePlanetaryHours(for: location) } | |
| // } | |
| // } | |
| // .ignoresSafeArea() | |
| // MARK: - Planetary Rulers (Names, Symbols, Colors) | |
| // Selectable planetary orders for hour-by-hour rotation | |
| private enum PlanetaryOrder { | |
| case chaldean | |
| case babylonian | |
| var sequence: [String] { | |
| switch self { | |
| case .chaldean: | |
| return ["Saturn", "Jupiter", "Mars", "Sun", "Venus", "Mercury", "Moon"] | |
| case .babylonian: | |
| // Geocentric (Babylonian) order from nearest to farthest with Sun between Venus and Mars | |
| return ["Moon", "Mercury", "Venus", "Sun", "Mars", "Jupiter", "Saturn"] | |
| } | |
| } | |
| } | |
| // Choose which order to use in the app | |
| private let planetaryOrder: PlanetaryOrder = .babylonian | |
| // Exact symbols provided by the user (Moon: ☽, Mars: ♂︎, Venus: ♀︎) | |
| private let planetSymbols: [String: String] = [ | |
| "Saturn": "♄", | |
| "Jupiter": "♃", | |
| "Mars": "♂︎", | |
| "Sun": "☉", | |
| "Venus": "♀︎", | |
| "Mercury": "☿", | |
| "Moon": "☽" | |
| ] | |
| // Display names | |
| private let planetNames: [String: String] = [ | |
| "Saturn": "Saturn", | |
| "Jupiter": "Jupiter", | |
| "Mars": "Mars", | |
| "Sun": "Sun", | |
| "Venus": "Venus", | |
| "Mercury": "Mercury", | |
| "Moon": "Moon" | |
| ] | |
| // --- Index-based models (Sun/Sunday start) --- | |
| // 0: Sun, 1: Moon, 2: Mars, 3: Mercury, 4: Jupiter, 5: Venus, 6: Saturn | |
| private let PLANETS: [String] = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"] | |
| private let PLANET_SYMBOLS_LIST: [String] = ["☉", "☽", "♂︎", "☿", "♃", "♀︎", "♄"] | |
| private let WEEKDAYS: [String] = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] | |
| // Babylonian forward stepping cycle expressed as planet INDEXES into PLANETS | |
| // Sun → Venus → Mercury → Moon → Saturn → Jupiter → Mars → (repeat) | |
| // With PLANETS indexing, that is: [0, 5, 3, 1, 6, 4, 2] | |
| private let BABYLONIAN_CYCLE: [Int] = [0, 5, 3, 1, 6, 4, 2] | |
| // Calendar weekday helpers: 1=Sunday ... 7=Saturday | |
| private func previousWeekday(_ weekday: Int) -> Int { | |
| return weekday == 1 ? 7 : (weekday - 1) | |
| } | |
| // Map civil weekday to planetary day ruler start index (Sunday=Sun, Monday=Moon, ... Saturday=Saturn) | |
| private func dayRulerIndex(for weekday: Int) -> Int { | |
| let order = planetaryOrder.sequence | |
| // Planet that rules the civil weekday name | |
| let dayRuler: String | |
| switch weekday { | |
| case 1: dayRuler = "Sun" // Sunday | |
| case 2: dayRuler = "Moon" // Monday | |
| case 3: dayRuler = "Mars" // Tuesday | |
| case 4: dayRuler = "Mercury" // Wednesday | |
| case 5: dayRuler = "Jupiter" // Thursday | |
| case 6: dayRuler = "Venus" // Friday | |
| case 7: dayRuler = "Saturn" // Saturday | |
| default: dayRuler = "Sun" | |
| } | |
| return order.firstIndex(of: dayRuler) ?? 0 | |
| } | |
| // Return the ruling planet *symbol* for the given planetary hour | |
| private func planetarySymbol(for hour: PlanetaryHour) -> String { | |
| // Determine civil weekday index: Apple: 1=Sunday..7=Saturday → 0..6 | |
| let wk = Calendar.current.component(.weekday, from: hour.startTime) - 1 | |
| let dayStartPlanetIndex = wk | |
| if hour.isDaytime && hour.hourNumber == 1 { | |
| return PLANET_SYMBOLS_LIST[dayStartPlanetIndex] | |
| } | |
| guard let cycleStart = BABYLONIAN_CYCLE.firstIndex(of: dayStartPlanetIndex) else { | |
| return PLANET_SYMBOLS_LIST[dayStartPlanetIndex] | |
| } | |
| let offsetWithinDay = (hour.isDaytime ? (hour.hourNumber - 1) : (12 + hour.hourNumber - 1)) | |
| let planetIndex = BABYLONIAN_CYCLE[(cycleStart + offsetWithinDay) % BABYLONIAN_CYCLE.count] | |
| return PLANET_SYMBOLS_LIST[planetIndex] | |
| } | |
| // Return the ruling planet *name* for the given planetary hour | |
| private func planetaryName(for hour: PlanetaryHour) -> String { | |
| // Determine civil weekday index: Apple: 1=Sunday..7=Saturday → 0..6 | |
| let wk = Calendar.current.component(.weekday, from: hour.startTime) - 1 | |
| let dayStartPlanetIndex = wk // index-aligned: Sunday→Sun(0), Monday→Moon(1), ... | |
| // First DAY hour resets to the weekday ruler | |
| if hour.isDaytime && hour.hourNumber == 1 { | |
| return PLANETS[dayStartPlanetIndex] | |
| } | |
| // Find where the dayStartPlanet sits inside the Babylonian forward cycle | |
| guard let cycleStart = BABYLONIAN_CYCLE.firstIndex(of: dayStartPlanetIndex) else { | |
| return PLANETS[dayStartPlanetIndex] | |
| } | |
| // Hour offset within the civil day: 0..23 (0 for Day Hour 1) | |
| let offsetWithinDay = (hour.isDaytime ? (hour.hourNumber - 1) : (12 + hour.hourNumber - 1)) | |
| // Advance forward within the Babylonian cycle | |
| let planetIndex = BABYLONIAN_CYCLE[(cycleStart + offsetWithinDay) % BABYLONIAN_CYCLE.count] | |
| return PLANETS[planetIndex] | |
| } | |
| // SwiftUI color mapping equivalent to the Objective‑C UIColor function | |
| private func colorForPlanetarySymbol(_ symbol: String) -> Color { | |
| switch symbol { | |
| case "☉": return .yellow | |
| case "☽": return .white | |
| case "♂︎": return .red | |
| case "☿": return .brown | |
| case "♃": return .orange | |
| case "♀︎": return .green | |
| case "♄": return .gray | |
| default: return .white | |
| } | |
| } | |
| // MARK: - Table View | |
| struct PlanetaryHoursTableView: View { | |
| let hours: [PlanetaryHour] | |
| private let timeFormatter: DateFormatter = { | |
| let formatter = DateFormatter() | |
| formatter.timeStyle = .medium | |
| return formatter | |
| }() | |
| var body: some View { | |
| ScrollView { | |
| VStack(spacing: 0) { | |
| // Day Hours Section | |
| Text("DAY") | |
| .font(.headline) | |
| .frame(maxWidth: .infinity) | |
| .background(Color.yellow.opacity(0.1)) | |
| .cornerRadius(10) | |
| ForEach(hours.filter { $0.isDaytime }) { hour in | |
| HourRowView(hour: hour, timeFormatter: timeFormatter) | |
| } | |
| } | |
| // .font(.default) | |
| .frame(maxWidth: .infinity) | |
| .background(Color.orange.opacity(0.1)) | |
| .cornerRadius(10) | |
| .padding([.horizontal]) | |
| VStack(spacing: 0) { | |
| // Night Hours Section | |
| Text("NIGHT") | |
| .font(.headline) | |
| .frame(maxWidth: .infinity) | |
| .background(Color.blue.opacity(0.2)) | |
| .padding() | |
| ForEach(hours.filter { !$0.isDaytime }) { hour in | |
| HourRowView(hour: hour, timeFormatter: timeFormatter) | |
| } | |
| } | |
| // .font(.default) | |
| .frame(maxWidth: .infinity) | |
| .background(Color.indigo.opacity(0.1)) | |
| .cornerRadius(10) | |
| } | |
| } | |
| } | |
| // MARK: - Hour Row View | |
| struct HourRowView: View { | |
| let hour: PlanetaryHour | |
| let timeFormatter: DateFormatter | |
| var body: some View { | |
| HStack(spacing: 12) { | |
| let sym = planetarySymbol(for: hour) | |
| let name = planetaryName(for: hour) | |
| HStack(spacing: 6) { | |
| Text("Hour \(hour.hourNumber)") | |
| Text(sym) | |
| .foregroundStyle(colorForPlanetarySymbol(sym)) | |
| } | |
| .font(.system(.body, design: .monospaced)) | |
| .frame(width: 140, alignment: .leading) | |
| Text(name) | |
| .font(.system(.body, design: .monospaced)) | |
| .frame(width: 90, alignment: .leading) | |
| VStack(alignment: .leading, spacing: 4) { | |
| Text(timeFormatter.string(from: hour.startTime)) | |
| .font(.caption) | |
| Text(timeFormatter.string(from: hour.endTime)) | |
| .font(.caption) | |
| } | |
| .frame(width: 140, alignment: .leading) | |
| Text(hour.durationString) | |
| .font(.system(.body, design: .monospaced)) | |
| .frame(width: 90, alignment: .trailing) | |
| } | |
| .padding(.vertical, 8) | |
| .padding(.horizontal, 12) | |
| // .background(hour.isDaytime ? Color.yellow.opacity(0.1) : Color.indigo.opacity(0.1)) | |
| } | |
| } | |
| private var dateRange: ClosedRange<Date> { | |
| let calendar = Calendar.current | |
| let today = Date() | |
| let sevenDaysBefore = calendar.date(byAdding: .day, value: -7, to: today)! | |
| let sevenDaysAfter = calendar.date(byAdding: .day, value: 7, to: today)! | |
| return sevenDaysBefore ... sevenDaysAfter | |
| } | |
| #Preview { | |
| ContentView() | |
| .preferredColorScheme(.dark) | |
| .dynamicTypeSize(DynamicTypeSize.medium) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // PlanetaryHours2025_ClaudeApp.swift | |
| // PlanetaryHours2025-Claude | |
| // | |
| // Created by Xcode Developer on 10/29/25. | |
| // | |
| import SwiftUI | |
| import WeatherKit | |
| import CoreLocation | |
| import Observation | |
| import Combine | |
| // MARK: - Models | |
| struct PlanetaryHour: Identifiable { | |
| let id = UUID() | |
| let hourNumber: Int | |
| let startTime: Date | |
| let endTime: Date | |
| let isDaytime: Bool | |
| var duration: TimeInterval { | |
| endTime.timeIntervalSince(startTime) | |
| } | |
| var durationString: String { | |
| let minutes = Int(duration / 60) | |
| let seconds = Int(duration.truncatingRemainder(dividingBy: 60)) | |
| return String(format: "%d:%02d", minutes, seconds) | |
| } | |
| } | |
| // MARK: - Location Manager | |
| @Observable class LocationManager: NSObject, CLLocationManagerDelegate { | |
| private let manager = CLLocationManager() | |
| var location: CLLocation? | |
| var authorizationStatus: CLAuthorizationStatus = .notDetermined | |
| var locationError: String? | |
| override init() { | |
| super.init() | |
| manager.delegate = self | |
| manager.desiredAccuracy = kCLLocationAccuracyBest | |
| authorizationStatus = manager.authorizationStatus | |
| } | |
| func requestLocation() { | |
| locationError = nil | |
| switch authorizationStatus { | |
| case .notDetermined: | |
| manager.requestWhenInUseAuthorization() | |
| case .authorizedWhenInUse, | |
| .authorizedAlways: | |
| manager.requestLocation() | |
| case .denied, | |
| .restricted: | |
| locationError = "Location access denied. Please enable location services in Settings." | |
| @unknown default: | |
| locationError = "Unknown authorization status" | |
| } | |
| } | |
| // CLLocationManagerDelegate methods | |
| func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { | |
| authorizationStatus = manager.authorizationStatus | |
| if authorizationStatus == .authorizedWhenInUse || authorizationStatus == .authorizedAlways { | |
| manager.requestLocation() | |
| } | |
| } | |
| func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { | |
| guard let location = locations.last else { return } | |
| self.location = location | |
| } | |
| func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { | |
| locationError = "Failed to get location: \(error.localizedDescription)" | |
| } | |
| } | |
| // MARK: - View Model | |
| @MainActor | |
| @Observable | |
| class PlanetaryHoursViewModel { | |
| var planetaryHours: [PlanetaryHour] = [] | |
| var isLoading = false | |
| var errorMessage: String? | |
| var selectedDate = Date() | |
| var locationName: String = "Unknown Location" | |
| private let weatherService = WeatherService.shared | |
| private let geocoder = CLGeocoder() | |
| func calculatePlanetaryHours(for location: CLLocation) async { | |
| isLoading = true | |
| errorMessage = nil | |
| // Get location name | |
| await getLocationName(for: location) | |
| do { | |
| // Get daily forecast for the selected date | |
| let startOfDay = Calendar.current.startOfDay(for: selectedDate) | |
| let endOfDay = Calendar.current.date(byAdding: .day, value: 1, to: startOfDay)! | |
| let dayWeather = try await weatherService.weather( | |
| for: location, | |
| including: .daily(startDate: startOfDay, endDate: endOfDay) | |
| ) | |
| guard let todayForecast = dayWeather.first else { | |
| errorMessage = "No weather data available for this date" | |
| isLoading = false | |
| return | |
| } | |
| // Get sunrise and sunset times | |
| guard let sunrise = todayForecast.sun.sunrise, | |
| let sunset = todayForecast.sun.sunset else { | |
| errorMessage = "Sunrise/sunset data not available for this location and date" | |
| isLoading = false | |
| return | |
| } | |
| // Calculate planetary hours | |
| var hours: [PlanetaryHour] = [] | |
| // Daytime hours (12 segments from sunrise to sunset) | |
| let dayDuration = sunset.timeIntervalSince(sunrise) | |
| let dayHourLength = dayDuration / 12.0 | |
| for i in 0 ..< 12 { | |
| let startTime = sunrise.addingTimeInterval(Double(i) * dayHourLength) | |
| let endTime = sunrise.addingTimeInterval(Double(i + 1) * dayHourLength) | |
| hours.append(PlanetaryHour( | |
| hourNumber: i + 1, | |
| startTime: startTime, | |
| endTime: endTime, | |
| isDaytime: true | |
| )) | |
| } | |
| // Nighttime hours (12 segments from sunset to next sunrise) | |
| // Get next day's sunrise | |
| let nextDay = Calendar.current.date(byAdding: .day, value: 1, to: startOfDay)! | |
| let dayAfter = Calendar.current.date(byAdding: .day, value: 2, to: startOfDay)! | |
| let nextDayWeather = try await weatherService.weather( | |
| for: location, | |
| including: .daily(startDate: nextDay, endDate: dayAfter) | |
| ) | |
| guard let tomorrowForecast = nextDayWeather.first, | |
| let nextSunrise = tomorrowForecast.sun.sunrise else { | |
| errorMessage = "Could not get next day's sunrise" | |
| isLoading = false | |
| return | |
| } | |
| let nightDuration = nextSunrise.timeIntervalSince(sunset) | |
| let nightHourLength = nightDuration / 12.0 | |
| for i in 0 ..< 12 { | |
| let startTime = sunset.addingTimeInterval(Double(i) * nightHourLength) | |
| let endTime = sunset.addingTimeInterval(Double(i + 1) * nightHourLength) | |
| hours.append(PlanetaryHour( | |
| hourNumber: i + 1, | |
| startTime: startTime, | |
| endTime: endTime, | |
| isDaytime: false | |
| )) | |
| } | |
| planetaryHours = hours | |
| } catch { | |
| errorMessage = "Error fetching weather data: \(error.localizedDescription)" | |
| } | |
| isLoading = false | |
| } | |
| private func getLocationName(for location: CLLocation) async { | |
| do { | |
| let placemarks = try await geocoder.reverseGeocodeLocation(location) | |
| if let placemark = placemarks.first { | |
| var components: [String] = [] | |
| if let city = placemark.locality { | |
| components.append(city) | |
| } | |
| if let state = placemark.administrativeArea { | |
| components.append(state) | |
| } | |
| if let country = placemark.country { | |
| components.append(country) | |
| } | |
| locationName = components.isEmpty ? "Unknown Location" : components.joined(separator: ", ") | |
| } | |
| } catch { | |
| locationName = "Lat: \(String(format: "%.4f", location.coordinate.latitude)), Lon: \(String(format: "%.4f", location.coordinate.longitude))" | |
| } | |
| } | |
| } | |
| // MARK: - App Entry Point | |
| @main | |
| struct PlanetaryHoursApp: App { | |
| var body: some Scene { | |
| WindowGroup { | |
| ContentView() | |
| } | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
ScreenRecording_10-30-2025.08-39-07_1.mp4