Add Orbit style for voting layout and entry views

- Add orbit case to VotingLayoutStyle enum with celestial design
- Create OrbitVotingView with center core and orbiting mood planets
- Add orbit case to DayViewStyle enum for entry list
- Create OrbitEntryView with mood icon center and orbiting day number
- Add orbit icons to voting and entry style pickers in CustomizeView

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-26 22:33:53 -06:00
parent 16af463569
commit f45f52ccbf
5 changed files with 361 additions and 0 deletions

View File

@@ -66,6 +66,8 @@ struct EntryListView: View {
motionStyle
case .micro:
microStyle
case .orbit:
orbitStyle
}
}
.accessibilityElement(children: .combine)
@@ -1767,6 +1769,163 @@ struct EntryListView: View {
)
)
}
// MARK: - Orbit Style (Celestial Circular)
private var orbitStyle: some View {
OrbitEntryView(
entry: entry,
imagePack: imagePack,
moodColor: moodColor,
textColor: textColor,
colorScheme: colorScheme,
isMissing: isMissing
)
}
}
// MARK: - Orbit Entry View
struct OrbitEntryView: View {
let entry: MoodEntryModel
let imagePack: MoodImages
let moodColor: Color
let textColor: Color
let colorScheme: ColorScheme
let isMissing: Bool
var body: some View {
HStack(spacing: 0) {
// Orbital system on left
orbitalSystem
.frame(width: 100, height: 100)
// Content on right
VStack(alignment: .leading, spacing: 8) {
moodDisplay
dateDisplay
}
.padding(.leading, 8)
Spacer()
chevron
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
.background(backgroundLayer)
.overlay(borderLayer)
}
private var orbitalSystem: some View {
ZStack {
// Orbital ring
Circle()
.stroke(
(colorScheme == .dark ? Color.white : Color.black).opacity(0.1),
lineWidth: 1
)
.frame(width: 80, height: 80)
// Center core (mood icon)
ZStack {
// Glow
Circle()
.fill(isMissing ? Color.gray.opacity(0.2) : moodColor.opacity(0.3))
.frame(width: 60, height: 60)
.blur(radius: 8)
// Planet
Circle()
.fill(isMissing ? Color.gray.opacity(0.4) : moodColor)
.frame(width: 44, height: 44)
.shadow(color: (isMissing ? Color.gray : moodColor).opacity(0.5), radius: 6, x: 0, y: 2)
// Icon
imagePack.icon(forMood: entry.mood)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24)
.foregroundColor(.white)
.accessibilityLabel(entry.mood.strValue)
}
// Orbiting day number
orbitingDay
}
}
private var orbitingDay: some View {
let angle = -Double.pi / 4 // Position at top-right
let radius: CGFloat = 40
return ZStack {
Circle()
.fill(Color.white.opacity(colorScheme == .dark ? 0.9 : 1.0))
.frame(width: 28, height: 28)
.shadow(color: .black.opacity(0.15), radius: 4)
Text(entry.forDate, format: .dateTime.day())
.font(.caption.weight(.bold))
.foregroundColor(.black.opacity(0.7))
}
.offset(x: cos(angle) * radius, y: sin(angle) * radius)
}
private var dateDisplay: some View {
VStack(alignment: .leading, spacing: 2) {
Text(entry.forDate, format: .dateTime.weekday(.wide))
.font(.body.weight(.semibold))
.foregroundColor(textColor)
Text(entry.forDate, format: .dateTime.month(.abbreviated).year())
.font(.caption)
.foregroundColor(textColor.opacity(0.5))
}
}
private var moodDisplay: some View {
Group {
if isMissing {
Text("No mood recorded")
.font(.subheadline)
.foregroundColor(.gray)
} else {
Text(entry.moodString)
.font(.subheadline.weight(.semibold))
.foregroundColor(moodColor)
.padding(.horizontal, 12)
.padding(.vertical, 5)
.background(
Capsule()
.fill(moodColor.opacity(0.15))
)
}
}
}
private var chevron: some View {
Image(systemName: "chevron.right")
.font(.subheadline.weight(.semibold))
.foregroundColor(textColor.opacity(0.3))
}
private var backgroundLayer: some View {
RoundedRectangle(cornerRadius: 20)
.fill(colorScheme == .dark ? Color(.systemGray6) : .white)
.shadow(
color: isMissing ? Color.black.opacity(0.05) : moodColor.opacity(0.15),
radius: 12,
x: 0,
y: 6
)
}
private var borderLayer: some View {
RoundedRectangle(cornerRadius: 20)
.stroke(
isMissing ? Color.gray.opacity(0.15) : moodColor.opacity(0.2),
lineWidth: 1
)
}
}
// MARK: - Motion Card with Accelerometer