Add MoonWatch app, widget extension, shared module, and .gitignore
Adds the real MoonWatch implementation on top of the initial Xcode template: new SwiftUI views (MoonDiskView, StarFieldView), the MoonWatchWidget extension, and a Shared module containing the moon phase calculator, shadow shape, and shared asset catalog. Also adds a Swift/Xcode/macOS .gitignore and untracks per-user xcuserdata files. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
97
MoonWatch/MoonDiskView.swift
Normal file
97
MoonWatch/MoonDiskView.swift
Normal file
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// MoonDiskView.swift
|
||||
// MoonWatch
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
private struct MoonSouthernHemisphereKey: EnvironmentKey {
|
||||
static let defaultValue = false
|
||||
}
|
||||
|
||||
extension EnvironmentValues {
|
||||
/// When true, waxing/waning orientation matches southern-sky convention (mirrored).
|
||||
var moonSouthernHemisphere: Bool {
|
||||
get { self[MoonSouthernHemisphereKey.self] }
|
||||
set { self[MoonSouthernHemisphereKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
/// In-app moon view: photographic disk + atmospheric halo, drop shadows,
|
||||
/// and limb stroke. The image+shadow rendering lives in `MoonImageDisk`
|
||||
/// (in `Shared/`) so the widget can reuse it.
|
||||
struct MoonDiskView: View {
|
||||
/// Synodic phase in `[0, 1)`; 0 = new, 0.5 = full.
|
||||
var normalizedPhase: Double
|
||||
@Environment(\.moonSouthernHemisphere) private var southernHemisphere
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
let side = min(geo.size.width, geo.size.height)
|
||||
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(
|
||||
RadialGradient(
|
||||
colors: [
|
||||
Color(red: 0.98, green: 0.94, blue: 0.78).opacity(0.32),
|
||||
Color(red: 0.55, green: 0.50, blue: 0.85).opacity(0.12),
|
||||
Color.clear,
|
||||
],
|
||||
center: .center,
|
||||
startRadius: side * 0.32,
|
||||
endRadius: side * 0.95
|
||||
)
|
||||
)
|
||||
.frame(width: side * 1.9, height: side * 1.9)
|
||||
.blendMode(.screen)
|
||||
.blur(radius: side * 0.05)
|
||||
|
||||
MoonImageDisk(
|
||||
normalizedPhase: normalizedPhase,
|
||||
southernHemisphere: southernHemisphere
|
||||
)
|
||||
.frame(width: side, height: side)
|
||||
}
|
||||
.shadow(color: Color(red: 0.98, green: 0.94, blue: 0.78).opacity(0.30),
|
||||
radius: side * 0.10)
|
||||
.shadow(color: Color(red: 0.45, green: 0.55, blue: 0.95).opacity(0.30),
|
||||
radius: side * 0.22)
|
||||
.frame(width: side, height: side)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Waxing Crescent") {
|
||||
ZStack {
|
||||
Color(red: 0.04, green: 0.05, blue: 0.12).ignoresSafeArea()
|
||||
MoonDiskView(normalizedPhase: 0.12)
|
||||
.frame(width: 280, height: 280)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("First Quarter") {
|
||||
ZStack {
|
||||
Color(red: 0.04, green: 0.05, blue: 0.12).ignoresSafeArea()
|
||||
MoonDiskView(normalizedPhase: 0.25)
|
||||
.frame(width: 280, height: 280)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Full") {
|
||||
ZStack {
|
||||
Color(red: 0.04, green: 0.05, blue: 0.12).ignoresSafeArea()
|
||||
MoonDiskView(normalizedPhase: 0.50)
|
||||
.frame(width: 280, height: 280)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Waning Gibbous") {
|
||||
ZStack {
|
||||
Color(red: 0.04, green: 0.05, blue: 0.12).ignoresSafeArea()
|
||||
MoonDiskView(normalizedPhase: 0.62)
|
||||
.frame(width: 280, height: 280)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user