diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index d4fb231..a77da43 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -186,6 +186,7 @@ D5D797E9298DCC7300738869 /* cheatbase.zip in Resources */ = {isa = PBXBuildFile; fileRef = D5D797E7298DC9E200738869 /* cheatbase.zip */; }; D5D7C1FA29E60EDE00663793 /* libDeltaFeatures.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D5D7C1F129E60DFF00663793 /* libDeltaFeatures.a */; }; D5D7C20429E60F2000663793 /* Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A9BFFD29DDECF100A8D610 /* Feature.swift */; }; + D5D7C20829E616CF00663793 /* FeatureContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D7C20729E616CF00663793 /* FeatureContainer.swift */; }; D5F82FB82981D3AC00B229AF /* LegacySearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */; }; /* End PBXBuildFile section */ @@ -419,6 +420,7 @@ D5D797E5298D946200738869 /* Contributor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contributor.swift; sourceTree = ""; }; D5D797E7298DC9E200738869 /* cheatbase.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = cheatbase.zip; sourceTree = ""; }; D5D7C1F129E60DFF00663793 /* libDeltaFeatures.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libDeltaFeatures.a; sourceTree = BUILT_PRODUCTS_DIR; }; + D5D7C20729E616CF00663793 /* FeatureContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureContainer.swift; sourceTree = ""; }; D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySearchBar.swift; sourceTree = ""; }; DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Delta.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -1013,6 +1015,7 @@ children = ( D5A9BFFD29DDECF100A8D610 /* Feature.swift */, D517F6BB29E737F5000D14D0 /* Types */, + D5D7C1FE29E60EF700663793 /* Protocols */, D5D7C1F829E60E8600663793 /* Extensions */, ); path = DeltaFeatures; @@ -1026,6 +1029,14 @@ path = Extensions; sourceTree = ""; }; + D5D7C1FE29E60EF700663793 /* Protocols */ = { + isa = PBXGroup; + children = ( + D5D7C20729E616CF00663793 /* FeatureContainer.swift */, + ); + path = Protocols; + sourceTree = ""; + }; FD1E8AE87FA2DB8793F7B937 /* Pods */ = { isa = PBXGroup; children = ( @@ -1504,6 +1515,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D5D7C20829E616CF00663793 /* FeatureContainer.swift in Sources */, D517F6BA29E730DA000D14D0 /* SettingsName.swift in Sources */, D5D7C20429E60F2000663793 /* Feature.swift in Sources */, D54F710429E89DFC009C069A /* NotificationName+Settings.swift in Sources */, diff --git a/DeltaFeatures/Feature.swift b/DeltaFeatures/Feature.swift index 902f065..2b0bee7 100644 --- a/DeltaFeatures/Feature.swift +++ b/DeltaFeatures/Feature.swift @@ -6,5 +6,43 @@ // Copyright © 2023 Riley Testut. All rights reserved. // -import Foundation +import SwiftUI +import Combine +@propertyWrapper @dynamicMemberLookup +public final class Feature +{ + public let name: LocalizedStringKey + public let description: LocalizedStringKey? + + // Assigned to property name. + public internal(set) var key: String = "" + + // Used for `SettingsUserInfoKey.name` value in .settingsDidChange notification. + public var settingsKey: SettingsName { + return SettingsName(rawValue: self.key) + } + + public var isEnabled: Bool { + get { + let isEnabled = UserDefaults.standard.bool(forKey: self.key) + return isEnabled + } + set { + self.objectWillChange.send() + UserDefaults.standard.set(newValue, forKey: self.key) + + NotificationCenter.default.post(name: .settingsDidChange, object: nil, userInfo: [SettingsUserInfoKey.name: self.settingsKey, SettingsUserInfoKey.value: newValue]) + } + } + + public var wrappedValue: Feature { + return self + } + + public init(name: LocalizedStringKey, description: LocalizedStringKey? = nil) + { + self.name = name + self.description = description + } +} diff --git a/DeltaFeatures/Protocols/FeatureContainer.swift b/DeltaFeatures/Protocols/FeatureContainer.swift new file mode 100644 index 0000000..7060aab --- /dev/null +++ b/DeltaFeatures/Protocols/FeatureContainer.swift @@ -0,0 +1,36 @@ +// +// FeatureContainer.swift +// DeltaFeatures +// +// Created by Riley Testut on 4/11/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation + +public protocol FeatureContainer +{ + static var shared: Self { get } +} + +public extension FeatureContainer +{ + var allFeatures: [Feature] { + let features = Mirror(reflecting: self).children.compactMap { (child) -> Feature? in + let feature = child.value as? Feature + return feature + } + return features + } + + func prepareFeatures() + { + // Assign keys to property names. + for case (let key?, let feature as Feature) in Mirror(reflecting: self).children + { + // Remove leading underscore. + let sanitizedKey = key.dropFirst() + feature.key = String(sanitizedKey) + } + } +}