// // 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 case neon = 6 // Synthwave arcade equalizer with glowing segments 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" case .neon: return "Neon" } } } enum PaywallStyle: Int, CaseIterable { case celestial = 0 // Celestial Self-Discovery - aurora, floating orbs case garden = 1 // Garden Growth - organic, blooming nature case neon = 2 // Neon Pulse - synthwave, energetic case minimal = 3 // Minimal Zen - clean, sophisticated var displayName: String { switch self { case .celestial: return "Celestial" case .garden: return "Garden" case .neon: return "Neon" case .minimal: return "Minimal" } } var description: String { switch self { case .celestial: return "Aurora lights & floating emotion orbs" case .garden: return "Blooming flowers & organic growth" case .neon: return "Synthwave energy & glowing pulses" case .minimal: return "Clean typography & subtle elegance" } } } 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 paywallStyle 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] } } }