Files
Reflect/Shared/Models/UserDefaultsStore.swift
Trey t f45f52ccbf 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>
2025-12-26 22:33:53 -06:00

307 lines
11 KiB
Swift

//
// UserDefaultsStore.swift
// Feels (iOS)
//
// Created by Trey Tartt on 1/22/22.
//
import Foundation
enum VotingLayoutStyle: Int, CaseIterable {
case horizontal = 0 // Current: 5 buttons in a row
case cards = 1 // Larger tappable cards with labels
case radial = 2 // Semi-circle/wheel arrangement
case stacked = 3 // Full-width vertical list
case aura = 4 // Atmospheric glowing orbs with flowing layout
case orbit = 5 // Celestial orbit with center core
var displayName: String {
switch self {
case .horizontal: return "Horizontal"
case .cards: return "Cards"
case .radial: return "Radial"
case .stacked: return "Stacked"
case .aura: return "Aura"
case .orbit: return "Orbit"
}
}
}
enum DayViewStyle: Int, CaseIterable {
case classic = 0 // Current card style with gradient icons
case minimal = 1 // Clean, simple flat cards
case compact = 2 // Dense timeline view
case bubble = 3 // Colorful full-width bubbles
case grid = 4 // 3 entries per row grid
case aura = 5 // Atmospheric glowing entries with giant typography
case chronicle = 6 // Editorial magazine with dramatic serif typography
case neon = 7 // Cyberpunk synthwave with glowing edges
case ink = 8 // Japanese zen calligraphy with brush strokes
case prism = 9 // Premium glassmorphism with light refraction
case tape = 10 // Retro cassette mixtape aesthetic
case morph = 11 // Liquid organic blob shapes
case stack = 12 // Layered paper notes with depth
case wave = 13 // Horizontal gradient river bands
case pattern = 14 // Mood icons as repeating background pattern
case leather = 15 // Skeuomorphic leather with stitching
case glass = 16 // iOS 26 liquid glass with variable blur
case motion = 17 // Accelerometer-driven parallax effect
case micro = 18 // Ultra compact single-line entries
case orbit = 19 // Celestial circular orbital arrangement
var displayName: String {
switch self {
case .classic: return "Classic"
case .minimal: return "Minimal"
case .compact: return "Compact"
case .bubble: return "Bubble"
case .grid: return "Grid"
case .aura: return "Aura"
case .chronicle: return "Chronicle"
case .neon: return "Neon"
case .ink: return "Ink"
case .prism: return "Prism"
case .tape: return "Tape"
case .morph: return "Morph"
case .stack: return "Stack"
case .wave: return "Wave"
case .pattern: return "Pattern"
case .leather: return "Leather"
case .glass: return "Glass"
case .motion: return "Motion"
case .micro: return "Micro"
case .orbit: return "Orbit"
}
}
var isGridLayout: Bool {
self == .grid
}
}
class UserDefaultsStore {
enum Keys: String {
case savedOnboardingData
case needsOnboarding
case useCloudKit
case deleteEnable
case mainViewTopHeaderIndex
case theme
case moodImages
case moodTint
case personalityPack
case customWidget
case customMoodTint
case customMoodTintUpdateNumber
case textColor
case showNSFW
case shape
case daysFilter
case firstLaunchDate
case hasActiveSubscription
case lastVotedDate
case votingLayoutStyle
case dayViewStyle
case privacyLockEnabled
case healthKitEnabled
case healthKitSyncEnabled
case contentViewCurrentSelectedHeaderViewBackDays
case contentViewHeaderTag
case contentViewHeaderTagViewOneViewType
case contentViewHeaderTagViewTwoViewType
case currentSelectedHeaderViewViewType
}
static func getOnboarding() -> OnboardingData {
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.savedOnboardingData.rawValue) as? Data,
let model = try? JSONDecoder().decode(OnboardingData.self, from: data) {
return model
} else {
return OnboardingData()
}
}
@discardableResult
static func saveOnboarding(onboardingData: OnboardingData) -> OnboardingData {
do {
let data = try JSONEncoder().encode(onboardingData)
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.savedOnboardingData.rawValue)
} catch {
print("Error saving onboarding: \(error)")
}
return UserDefaultsStore.getOnboarding()
}
static func moodMoodImagable() -> MoodImagable.Type {
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.moodImages.rawValue) as? Int,
let model = MoodImages.init(rawValue: data) {
return model.moodImages
} else {
return MoodImages.FontAwesome.moodImages
}
}
static func moodTintable() -> MoodTintable.Type {
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.moodTint.rawValue) as? Int,
let model = MoodTints.init(rawValue: data) {
return model.moodTints
} else {
return MoodTints.Default.moodTints
}
}
static func personalityPackable() -> PersonalityPack {
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.personalityPack.rawValue) as? Int,
let model = PersonalityPack.init(rawValue: data) {
return model
} else {
return PersonalityPack.Default
}
}
static func theme() -> Theme {
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.theme.rawValue) as? Int,
let model = Theme.init(rawValue: data) {
return model
} else {
return Theme.system
}
}
static func getCustomWidgets() -> [CustomWidgetModel] {
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customWidget.rawValue) as? Data,
let model = try? JSONDecoder().decode([CustomWidgetModel].self, from: data) {
return model
} else {
GroupUserDefaults.groupDefaults.removeObject(forKey: UserDefaultsStore.Keys.customWidget.rawValue)
let widget = CustomWidgetModel.randomWidget
widget.isSaved = true
let widgets = [widget]
guard let data = try? JSONEncoder().encode(widgets) else {
return widgets
}
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
if let savedData = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customWidget.rawValue) as? Data,
let models = try? JSONDecoder().decode([CustomWidgetModel].self, from: savedData) {
return models.sorted { $0.createdDate < $1.createdDate }
} else {
return widgets
}
}
}
@discardableResult
static func saveCustomWidget(widgetModel: CustomWidgetModel, inUse: Bool) -> [CustomWidgetModel] {
do {
var existingWidgets = getCustomWidgets()
if let exisitingWidget = existingWidgets.firstIndex(where: {
$0.uuid == widgetModel.uuid
}) {
existingWidgets.remove(at: exisitingWidget)
// give it differnet uuid so the view updates
widgetModel.uuid = UUID().uuidString
}
if inUse {
existingWidgets.forEach({
$0.inUse = false
})
widgetModel.inUse = true
}
existingWidgets.append(widgetModel)
existingWidgets.forEach({
$0.isSaved = true
})
let data = try JSONEncoder().encode(existingWidgets)
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
} catch {
print("Error saving custom widget: \(error)")
}
return UserDefaultsStore.getCustomWidgets()
}
@discardableResult
static func deleteCustomWidget(withUUID uuid: String) -> [CustomWidgetModel] {
do {
var existingWidgets = getCustomWidgets()
if let exisitingWidget = existingWidgets.firstIndex(where: {
$0.uuid == uuid
}) {
existingWidgets.remove(at: exisitingWidget)
}
if existingWidgets.count == 0 {
let widget = CustomWidgetModel.randomWidget
widget.isSaved = true
widget.inUse = true
existingWidgets.append(widget)
}
if existingWidgets.first(where: { $0.inUse == true }) == nil {
existingWidgets.first?.inUse = true
}
let data = try JSONEncoder().encode(existingWidgets)
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
} catch {
print("Error deleting custom widget: \(error)")
}
return UserDefaultsStore.getCustomWidgets()
}
static func getCustomMoodTint() -> SavedMoodTint {
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customMoodTint.rawValue) as? Data{
do {
let model = try JSONDecoder().decode(SavedMoodTint.self, from: data)
return model
} catch {
print(error)
}
}
return SavedMoodTint()
}
static func getCustomBGShape() -> BGShape {
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.shape.rawValue) as? Int,
let model = BGShape.init(rawValue: data) {
return model
} else {
return BGShape.circle
}
}
@discardableResult
static func saveCustomMoodTint(customTint: SavedMoodTint) -> SavedMoodTint {
do {
let data = try JSONEncoder().encode(customTint)
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customMoodTint.rawValue)
} catch {
print("Error saving custom mood tint: \(error)")
}
return UserDefaultsStore.getCustomMoodTint()
}
@discardableResult
static func saveDaysFilter(days: [Int]) -> [Int] {
GroupUserDefaults.groupDefaults.set(days, forKey: UserDefaultsStore.Keys.daysFilter.rawValue)
return UserDefaultsStore.getDaysFilter()
}
static func getDaysFilter() -> [Int] {
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.daysFilter.rawValue) as? [Int] {
return data
} else {
return [1,2,3,4,5,6,7]
}
}
}