Files
Reflect/Shared/Views/CustomizeView/SubViews/VotingLayoutPickerView.swift
Trey t 0442eab1f8 Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers,
StoreKit product IDs, data store filenames, URL schemes, logger subsystems,
Swift identifiers, user-facing strings (7 languages), file names, directory
names, Xcode project, schemes, assets, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:47:16 -06:00

189 lines
7.5 KiB
Swift

//
// VotingLayoutPickerView.swift
// Reflect (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.votingLayoutStyle.rawValue, store: GroupUserDefaults.groupDefaults) private var votingLayoutStyle: Int = 0
private var textColor: Color { theme.currentTheme.labelColor }
private var currentLayout: VotingLayoutStyle {
VotingLayoutStyle(rawValue: votingLayoutStyle) ?? .horizontal
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Voting Layout")
.font(.headline)
.foregroundColor(textColor)
.padding(.horizontal)
.padding(.top)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(VotingLayoutStyle.allCases, id: \.rawValue) { layout in
Button(action: {
if UIAccessibility.isReduceMotionEnabled {
votingLayoutStyle = layout.rawValue
} else {
withAnimation(.easeInOut(duration: 0.2)) {
votingLayoutStyle = layout.rawValue
}
}
AnalyticsManager.shared.track(.votingLayoutChanged(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(width: 70)
.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)
}
.background(theme.currentTheme.secondaryBGColor)
.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 .stacked:
VStack(spacing: 3) {
ForEach(0..<4) { _ in
RoundedRectangle(cornerRadius: 2)
.frame(width: 32, height: 6)
}
}
case .aura:
// Glowing orbs in 2 rows
VStack(spacing: 4) {
HStack(spacing: 6) {
ForEach(0..<3, id: \.self) { _ in
ZStack {
Circle()
.fill(RadialGradient(colors: [.green.opacity(0.5), .clear], center: .center, startRadius: 0, endRadius: 8))
.frame(width: 14, height: 14)
Circle()
.fill(.green)
.frame(width: 8, height: 8)
}
}
}
HStack(spacing: 10) {
ForEach(0..<2, id: \.self) { _ in
ZStack {
Circle()
.fill(RadialGradient(colors: [.green.opacity(0.5), .clear], center: .center, startRadius: 0, endRadius: 8))
.frame(width: 14, height: 14)
Circle()
.fill(.green)
.frame(width: 8, height: 8)
}
}
}
}
case .orbit:
// Center core with orbiting planets
ZStack {
// Orbital ring
Circle()
.stroke(Color.primary.opacity(0.2), lineWidth: 1)
.frame(width: 32, height: 32)
// Center core
Circle()
.fill(Color.primary.opacity(0.8))
.frame(width: 8, height: 8)
// Orbiting planets
ForEach(0..<5, id: \.self) { index in
Circle()
.fill(Color.accentColor)
.frame(width: 6, height: 6)
.offset(orbitOffset(index: index, total: 5, radius: 16))
}
}
case .neon:
// Equalizer bars
ZStack {
// Grid background hint
RoundedRectangle(cornerRadius: 4)
.fill(Color.black)
.frame(width: 36, height: 36)
// Equalizer bars
HStack(spacing: 2) {
ForEach(0..<5, id: \.self) { index in
let heights: [CGFloat] = [24, 18, 14, 10, 8]
RoundedRectangle(cornerRadius: 1)
.fill(
LinearGradient(
colors: [Color(red: 0, green: 1, blue: 0.82), Color(red: 1, green: 0, blue: 0.8)],
startPoint: .top,
endPoint: .bottom
)
)
.frame(width: 4, height: heights[index])
}
}
}
}
}
private func orbitOffset(index: Int, total: Int, radius: CGFloat) -> CGSize {
// Start from top (-π/2) and go clockwise
let startAngle = -Double.pi / 2
let angleStep = (2 * Double.pi) / Double(total)
let angle = startAngle + angleStep * Double(index)
return CGSize(
width: radius * CGFloat(cos(angle)),
height: radius * CGFloat(sin(angle))
)
}
}
struct VotingLayoutPickerView_Previews: PreviewProvider {
static var previews: some View {
VotingLayoutPickerView()
.padding()
}
}