- 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>
251 lines
8.7 KiB
Swift
251 lines
8.7 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
|
|
|
|
var displayName: String {
|
|
switch self {
|
|
case .horizontal: return "Horizontal"
|
|
case .cards: return "Cards"
|
|
case .radial: return "Radial"
|
|
case .stacked: return "Stacked"
|
|
}
|
|
}
|
|
}
|
|
|
|
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 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()
|
|
}
|
|
}
|
|
|
|
static func saveOnboarding(onboardingData: OnboardingData) -> OnboardingData {
|
|
do {
|
|
let data = try JSONEncoder().encode(onboardingData)
|
|
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.savedOnboardingData.rawValue)
|
|
return UserDefaultsStore.getOnboarding()
|
|
} catch {
|
|
fatalError("error saving")
|
|
}
|
|
}
|
|
|
|
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]
|
|
let data = try! JSONEncoder().encode(widgets)
|
|
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
|
|
|
if let data = GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.customWidget.rawValue) as? Data,
|
|
let models = try? JSONDecoder().decode([CustomWidgetModel].self, from: data) {
|
|
let sorted = models.sorted(by: {
|
|
$0.createdDate < $1.createdDate
|
|
})
|
|
return sorted
|
|
} else {
|
|
fatalError("error getting 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)
|
|
return UserDefaultsStore.getCustomWidgets()
|
|
} catch {
|
|
fatalError("error saving")
|
|
}
|
|
}
|
|
|
|
@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 let _ = existingWidgets.first(where: {
|
|
$0.inUse == true
|
|
}) {} else {
|
|
if let first = existingWidgets.first {
|
|
first.inUse = true
|
|
}
|
|
}
|
|
|
|
let data = try JSONEncoder().encode(existingWidgets)
|
|
GroupUserDefaults.groupDefaults.set(data, forKey: UserDefaultsStore.Keys.customWidget.rawValue)
|
|
return UserDefaultsStore.getCustomWidgets()
|
|
} catch {
|
|
fatalError("error saving")
|
|
}
|
|
}
|
|
|
|
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)
|
|
return UserDefaultsStore.getCustomMoodTint()
|
|
} catch {
|
|
print(error)
|
|
fatalError("error saving")
|
|
}
|
|
}
|
|
|
|
@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]
|
|
}
|
|
}
|
|
}
|
|
|