Redesign Day view with switchable voting layouts and modern styling

- Add 4 voting layout styles: horizontal, cards, radial, stacked
- Add color-filled backgrounds to mood entries (tinted by mood color)
- Add sticky month headers with blur material effect
- Add voting layout picker to Customize tab
- Add haptic feedback on mood selection
- Improve typography and spacing throughout

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-09 23:44:28 -06:00
parent f2565678be
commit f7ac2085b8
6 changed files with 426 additions and 105 deletions

View File

@@ -21,6 +21,8 @@ struct CustomizeView: View {
IconPickerView()
ThemePickerView()
Divider()
VotingLayoutPickerView()
Divider()
SampleEntryView()
ImagePackPickerView()
}

View File

@@ -0,0 +1,118 @@
//
// VotingLayoutPickerView.swift
// Feels (iOS)
//
// Created by Claude Code on 12/9/24.
//
import SwiftUI
struct VotingLayoutPickerView: View {
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
@AppStorage(UserDefaultsStore.Keys.textColor.rawValue, store: GroupUserDefaults.groupDefaults) private var textColor: Color = DefaultTextColor.textColor
@AppStorage(UserDefaultsStore.Keys.votingLayoutStyle.rawValue, store: GroupUserDefaults.groupDefaults) private var votingLayoutStyle: Int = 0
private var currentLayout: VotingLayoutStyle {
VotingLayoutStyle(rawValue: votingLayoutStyle) ?? .horizontal
}
var body: some View {
ZStack {
theme.currentTheme.secondaryBGColor
VStack(alignment: .leading, spacing: 12) {
Text("Voting Layout")
.font(.headline)
.foregroundColor(textColor)
.padding(.horizontal)
.padding(.top)
HStack(spacing: 8) {
ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in
Button(action: {
withAnimation(.easeInOut(duration: 0.2)) {
votingLayoutStyle = layout.rawValue
}
EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName])
}) {
VStack(spacing: 6) {
layoutIcon(for: layout)
.frame(width: 44, height: 44)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.6))
Text(layout.displayName)
.font(.caption)
.foregroundColor(currentLayout == layout ? .accentColor : textColor.opacity(0.8))
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(currentLayout == layout ? Color.accentColor.opacity(0.15) : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(currentLayout == layout ? Color.accentColor : Color.clear, lineWidth: 2)
)
}
.buttonStyle(.plain)
}
}
.padding(.horizontal)
.padding(.bottom)
}
}
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
@ViewBuilder
private func layoutIcon(for layout: VotingLayoutStyle) -> some View {
switch layout {
case .horizontal:
HStack(spacing: 4) {
ForEach(0..<5) { _ in
Circle()
.frame(width: 6, height: 6)
}
}
case .cards:
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())], spacing: 3) {
ForEach(0..<6) { _ in
RoundedRectangle(cornerRadius: 2)
.frame(width: 10, height: 12)
}
}
case .radial:
ZStack {
ForEach(0..<5) { index in
Circle()
.frame(width: 6, height: 6)
.offset(radialOffset(index: index, total: 5, radius: 16))
}
}
case .stacked:
VStack(spacing: 3) {
ForEach(0..<4) { _ in
RoundedRectangle(cornerRadius: 2)
.frame(width: 32, height: 6)
}
}
}
}
private func radialOffset(index: Int, total: Int, radius: CGFloat) -> CGSize {
let angle = Double.pi - (Double.pi * Double(index) / Double(total - 1))
return CGSize(
width: radius * CGFloat(cos(angle)),
height: -radius * CGFloat(sin(angle)) + 4
)
}
}
struct VotingLayoutPickerView_Previews: PreviewProvider {
static var previews: some View {
VotingLayoutPickerView()
.padding()
}
}