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:
@@ -21,6 +21,8 @@ struct CustomizeView: View {
|
||||
IconPickerView()
|
||||
ThemePickerView()
|
||||
Divider()
|
||||
VotingLayoutPickerView()
|
||||
Divider()
|
||||
SampleEntryView()
|
||||
ImagePackPickerView()
|
||||
}
|
||||
|
||||
118
Shared/Views/CustomizeView/SubViews/VotingLayoutPickerView.swift
Normal file
118
Shared/Views/CustomizeView/SubViews/VotingLayoutPickerView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user