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:
@@ -82,6 +82,8 @@ struct AddMoodHeaderView: View {
|
||||
StackedVotingView(moodTint: moodTint, onMoodSelected: addItem)
|
||||
case .aura:
|
||||
AuraVotingView(moodTint: moodTint, onMoodSelected: addItem)
|
||||
case .orbit:
|
||||
OrbitVotingView(moodTint: moodTint, onMoodSelected: addItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,6 +371,135 @@ struct AuraVotingView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Layout 6: Orbit (Celestial circular arrangement)
|
||||
struct OrbitVotingView: View {
|
||||
let moodTint: MoodTints
|
||||
let onMoodSelected: (Mood) -> Void
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
@State private var centerPulse: CGFloat = 1.0
|
||||
|
||||
private var isDark: Bool { colorScheme == .dark }
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
let size = min(geo.size.width, geo.size.height)
|
||||
let centerX = geo.size.width / 2
|
||||
let centerY = size / 2
|
||||
let orbitRadius = size * 0.38
|
||||
|
||||
ZStack {
|
||||
orbitalRing(radius: orbitRadius, centerX: centerX, centerY: centerY)
|
||||
centerCore(centerX: centerX, centerY: centerY)
|
||||
moodPlanets(radius: orbitRadius, centerX: centerX, centerY: centerY)
|
||||
}
|
||||
}
|
||||
.frame(height: 260)
|
||||
.onAppear {
|
||||
withAnimation(.easeInOut(duration: 2.0).repeatForever(autoreverses: true)) {
|
||||
centerPulse = 1.1
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .contain)
|
||||
.accessibilityLabel(String(localized: "Mood selection"))
|
||||
}
|
||||
|
||||
private func orbitalRing(radius: CGFloat, centerX: CGFloat, centerY: CGFloat) -> some View {
|
||||
let ringColor = isDark ? Color.white : Color.black
|
||||
return Circle()
|
||||
.stroke(ringColor.opacity(0.08), lineWidth: 1)
|
||||
.frame(width: radius * 2, height: radius * 2)
|
||||
.position(x: centerX, y: centerY)
|
||||
}
|
||||
|
||||
private func centerCore(centerX: CGFloat, centerY: CGFloat) -> some View {
|
||||
let glowOpacity = isDark ? 0.15 : 0.3
|
||||
let coreOpacity = isDark ? 0.9 : 1.0
|
||||
|
||||
return ZStack {
|
||||
Circle()
|
||||
.fill(Color.white.opacity(glowOpacity))
|
||||
.frame(width: 80, height: 80)
|
||||
.scaleEffect(centerPulse)
|
||||
|
||||
Circle()
|
||||
.fill(Color.white.opacity(coreOpacity))
|
||||
.frame(width: 36, height: 36)
|
||||
.shadow(color: .white.opacity(0.5), radius: 10)
|
||||
}
|
||||
.position(x: centerX, y: centerY)
|
||||
}
|
||||
|
||||
private func moodPlanets(radius: CGFloat, centerX: CGFloat, centerY: CGFloat) -> some View {
|
||||
ForEach(Array(Mood.allValues.enumerated()), id: \.element.id) { index, mood in
|
||||
orbitMoodButton(
|
||||
for: mood,
|
||||
index: index,
|
||||
radius: radius,
|
||||
centerX: centerX,
|
||||
centerY: centerY
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func orbitMoodButton(for mood: Mood, index: Int, radius: CGFloat, centerX: CGFloat, centerY: CGFloat) -> some View {
|
||||
let angle = -Double.pi / 2 + (2 * Double.pi / 5) * Double(index)
|
||||
let posX = centerX + cos(angle) * radius
|
||||
let posY = centerY + sin(angle) * radius
|
||||
let color = moodTint.color(forMood: mood)
|
||||
|
||||
return Button(action: { onMoodSelected(mood) }) {
|
||||
OrbitMoodButtonContent(mood: mood, color: color)
|
||||
}
|
||||
.buttonStyle(OrbitButtonStyle(color: color))
|
||||
.position(x: posX, y: posY)
|
||||
.accessibilityLabel(mood.strValue)
|
||||
.accessibilityHint(String(localized: "Select this mood"))
|
||||
}
|
||||
}
|
||||
|
||||
struct OrbitMoodButtonContent: View {
|
||||
let mood: Mood
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(color.opacity(0.2))
|
||||
.frame(width: 70, height: 70)
|
||||
|
||||
Circle()
|
||||
.fill(color)
|
||||
.frame(width: 52, height: 52)
|
||||
.shadow(color: color.opacity(0.6), radius: 8, x: 0, y: 2)
|
||||
|
||||
mood.icon
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 28, height: 28)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Button style for orbit moods
|
||||
struct OrbitButtonStyle: ButtonStyle {
|
||||
let color: Color
|
||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.scaleEffect(configuration.isPressed ? 1.15 : 1.0)
|
||||
.shadow(
|
||||
color: configuration.isPressed ? color.opacity(0.8) : Color.clear,
|
||||
radius: configuration.isPressed ? 20 : 0,
|
||||
x: 0,
|
||||
y: 0
|
||||
)
|
||||
.animation(reduceMotion ? nil : .spring(response: 0.3, dampingFraction: 0.6), value: configuration.isPressed)
|
||||
}
|
||||
}
|
||||
|
||||
// Custom button style for aura with glow effect on press
|
||||
struct AuraButtonStyle: ButtonStyle {
|
||||
let color: Color
|
||||
|
||||
Reference in New Issue
Block a user