// // ModelColorConfiguration.swift // VDS // // Created by Matt Bruce on 8/4/22. // import Foundation import UIKit public typealias ObjectColorable = Colorable & Initable & ObjectWithable /// Meant to be used in a Object that implements the following interfaces for 2 possible color combinations /// - Surfaceable (var surface: Surface) /// /// let model = TestModel() /// model.surface = .light /// /// let config = SurfaceColorConfiguration() /// config.lightColor = .black /// config.darkColor = .white /// /// let textColor = config.getColor(model) //returns .black /// /// You can pass in a Surfaceable object and this will return the corresponding Color based on the object's surface property. open class SurfaceColorConfiguration: ObjectColorable { public typealias ObjectType = Surfaceable public var lightColor: UIColor = .clear public var darkColor: UIColor = .clear required public init(){} /// Inititialization /// - Parameters: /// - lightColor: Color used for a light surface /// - darkColor: Color used for a dark surface public init(_ lightColor: UIColor, _ darkColor: UIColor) { self.lightColor = lightColor self.darkColor = darkColor } /// Will get the UIColor for the surface passed in /// - Parameter surface: surface you currently have /// - Returns: color corresponding to the surface you passed public func getColor(_ surface: Surface) -> UIColor { return surface == .light ? lightColor : darkColor } /// Colorable Generic method that will only take in any object that has a Surfacable implementation /// - Parameter object: any Surfacable object type /// - Returns: UIColor for the object type you passed in public func getColor(_ object: any ObjectType) -> UIColor { return getColor(object.surface) } } /// Struct to Hold onto KeyType and SurfaceConfiguration public struct KeyColorConfiguration { var key: KeyType var surfaceConfig: SurfaceColorConfiguration } // Protocol to Hold onto the relationship of KeyType and SurfaceConfiguration public protocol KeyColorConfigurable: ObjectColorable { associatedtype KeyType: Equatable var keyColors: [KeyColorConfiguration] { get set } } extension KeyColorConfigurable { /// Generic Method to set a KeyColorConfiguration with the parameters given /// - Parameters: /// - lightColor: Color used for a light /// - darkColor: Color used for a dark /// - key: <#key description#> public func setSurfaceColors(_ lightColor: UIColor, _ darkColor: UIColor, forKey key: KeyType) { keyColors.append(.init(key: key, surfaceConfig: .init(lightColor, darkColor))) } /// Removes all keyColors public func reset() { keyColors.removeAll() } } extension KeyColorConfigurable where ObjectType: Surfaceable { /// Default Implementation for when the ObjectType is a Surfacable /// - Parameters: /// - object: Surfaceable ObjectType /// - key: KeyPath to the property of the ObjectType /// - Returns: UIColor corresponding the the key and surface public func getColor(for object: ObjectType, with key: KeyType) -> UIColor { if let keyColor = keyColors.first(where: {$0.key == key }) { return keyColor.surfaceConfig.getColor(object) } else { return .clear //default } } } /// ColorConfiguration for UIControls and will implement KeyColorConfigurale. This will then use the /// register a SurfaceColorConfiguration for each state. public class ControlColorConfiguration: KeyColorConfigurable { public typealias KeyType = UIControl.State public typealias ObjectType = Surfaceable & UIControl public var keyColors: [KeyColorConfiguration] = [] public required init() { } /// Helper method to set the SurfaceColorConfiguration for a state /// - Parameters: /// - lightColor: Color used for a light /// - darkColor: Color used for a dark /// - state: UIControlState you are keying off of. public func setSurfaceColors(_ lightColor: UIColor, _ darkColor: UIColor, forState state: KeyType) { setSurfaceColors(lightColor, darkColor, forKey: state) } /// Colorable implementation for getColor that where the object passed in will be of a UIControl as well as implementing Surfaceable. /// - Parameter object: Object that is of UIControl and implements Surfacable /// - Returns: UIColor corresponding to the UIControl.state and surface property of this object. public func getColor(_ object: any ObjectType) -> UIColor { let state = object.state let surface = object.surface if let keyColor = keyColors.first(where: {$0.key == state }) { return keyColor.surfaceConfig.getColor(surface) } else if let keyColor = keyColors.first(where: {$0.key.isSubset(of: state) }) { return keyColor.surfaceConfig.getColor(surface) } else { return .clear } } } ///Meant to be used with any object that implements Surfaceable and Disabling. More than likely this is any View. public class ViewColorConfiguration: KeyColorConfigurable { public typealias KeyType = Bool public typealias ObjectType = Surfaceable & Disabling public var keyColors: [KeyColorConfiguration] = [] public required init() { } /// Helper method to set the SurfaceColorConfiguration based on whether the object is disabled or not. /// - Parameters: /// - lightColor: Color used for a light /// - darkColor: Color used for a dark /// - disabled: True/False for disabled property public func setSurfaceColors(_ lightColor: UIColor, _ darkColor: UIColor, forDisabled disabled: KeyType) { setSurfaceColors(lightColor, darkColor, forKey: disabled) } /// Colorable implementation for getColor that where the object passed in will any object that implementing Surfaceable and Disabling. /// - Parameter object: Object that implements Surfaceable and Disabling /// - Returns: UIColor correspoding to either true/false for the disabled state and surface public func getColor(_ object: ObjectType) -> UIColor { if let keyColor = keyColors.first(where: {$0.key == object.disabled }) { return keyColor.surfaceConfig.getColor(object) } else { return .clear //default } } } /// Generic Class that allows reflection for a object's specific property (equatable) to be defined as the lookup for a SurfaceConfiguration public class KeyedColorConfiguration : KeyColorConfigurable { //Type of Class that at least implements Surfaceable public typealias ObjectType = ObjectType //where the value exist for the object public var keyPath: KeyPath? /// Array of structs where the KeyValue is registered against a SurfaceColorConfiguration public var keyColors: [KeyColorConfiguration] = [] public required init() {} //this must be used public required convenience init(keyPath: KeyPath) { self.init() self.keyPath = keyPath } /// Finds the KeyColorConfiguration for the Object passed in. /// - Parameter object: Object should be of Surfaceable /// - Returns: KeyType for the Object passed in public func getKeyValue(_ object: ObjectType) -> KeyType? { guard let keyPath else { fatalError("keyPath must not be empty, make sure you initialize this class using init(keyPath: \\Object.property) method") } return object[keyPath: keyPath] } /// Colorable implementation for getColor that where the object passed in will any object that implementing Surfaceable for the KeyPath registered. /// - Parameter object: Object matching the type registered /// - Returns: UIColor for the Object pertaining to the KeyPath it was registered public func getColor(_ object: ObjectType) -> UIColor { if let key = getKeyValue(object), let keyColor = keyColors.first(where: {$0.key == key }) { return keyColor.surfaceConfig.getColor(object) } else { return .clear //default } } }