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:
2026-05-07 21:05:16 -07:00
parent 1698e3ce15
commit 87f8c7173c
17 changed files with 1211 additions and 24 deletions

View File

@@ -0,0 +1,92 @@
//
// StarFieldView.swift
// MoonWatch
//
import SwiftUI
struct StarFieldView: View {
var starCount: Int = 160
@State private var stars: [Star] = []
private struct Star: Identifiable {
let id = UUID()
let position: CGPoint // 0...1 normalized
let radius: CGFloat
let baseOpacity: Double
let twinklePhase: Double
let twinkleSpeed: Double
let warm: Bool
}
var body: some View {
GeometryReader { geo in
TimelineView(.animation(minimumInterval: 1.0 / 12.0)) { ctx in
Canvas { context, size in
let t = ctx.date.timeIntervalSinceReferenceDate
for star in stars {
let twinkle = (sin(t * star.twinkleSpeed + star.twinklePhase) + 1) / 2
let alpha = star.baseOpacity * (0.35 + 0.65 * twinkle)
let center = CGPoint(
x: star.position.x * size.width,
y: star.position.y * size.height
)
let rect = CGRect(
x: center.x - star.radius,
y: center.y - star.radius,
width: star.radius * 2,
height: star.radius * 2
)
let color: Color = star.warm
? Color(red: 1.0, green: 0.92, blue: 0.78).opacity(alpha)
: Color(red: 0.92, green: 0.95, blue: 1.0).opacity(alpha)
context.fill(Path(ellipseIn: rect), with: .color(color))
if star.radius > 1.1 {
// Soft halo for the larger stars.
let halo = rect.insetBy(dx: -star.radius * 1.6, dy: -star.radius * 1.6)
context.fill(
Path(ellipseIn: halo),
with: .color(color.opacity(0.20))
)
}
}
}
}
.onAppear {
if stars.isEmpty {
stars = Self.generate(count: starCount)
}
}
}
.ignoresSafeArea()
.allowsHitTesting(false)
}
private static func generate(count: Int) -> [Star] {
var rng = SystemRandomNumberGenerator()
return (0..<count).map { _ in
let big = Double.random(in: 0...1, using: &rng) > 0.88
return Star(
position: CGPoint(
x: CGFloat.random(in: 0...1, using: &rng),
y: CGFloat.random(in: 0...1, using: &rng)
),
radius: big
? CGFloat.random(in: 1.4...2.2, using: &rng)
: CGFloat.random(in: 0.4...1.1, using: &rng),
baseOpacity: Double.random(in: 0.35...0.95, using: &rng),
twinklePhase: Double.random(in: 0...(2 * .pi), using: &rng),
twinkleSpeed: Double.random(in: 0.5...1.7, using: &rng),
warm: Double.random(in: 0...1, using: &rng) > 0.75
)
}
}
}
#Preview {
ZStack {
Color(red: 0.03, green: 0.04, blue: 0.10).ignoresSafeArea()
StarFieldView()
}
}