|
|
@@ -0,0 +1,116 @@
|
|
|
+//
|
|
|
+// Group.swift
|
|
|
+// Todos
|
|
|
+//
|
|
|
+// Created by Sam Jaffe on 3/2/26.
|
|
|
+//
|
|
|
+
|
|
|
+import Foundation
|
|
|
+import SwiftUI
|
|
|
+
|
|
|
+// https://gist.github.com/peterfriese/bb2fc5df202f6a15cc807bd87ff15193
|
|
|
+// Inspired by https://cocoacasts.com/from-hex-to-uicolor-and-back-in-swift
|
|
|
+// Make Color codable. This includes support for transparency.
|
|
|
+// See https://www.digitalocean.com/community/tutorials/css-hex-code-colors-alpha-values
|
|
|
+extension Color: @retroactive Codable {
|
|
|
+#if os(macOS)
|
|
|
+ fileprivate typealias UnifiedColor = NSColor
|
|
|
+#endif
|
|
|
+
|
|
|
+#if os(iOS)
|
|
|
+ fileprivate typealias UnifiedColor = UIColor
|
|
|
+#endif
|
|
|
+
|
|
|
+ init(hex: String) {
|
|
|
+ var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
+ hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
|
|
|
+
|
|
|
+ var rgb: UInt64 = 0
|
|
|
+
|
|
|
+ var r: CGFloat = 0.0
|
|
|
+ var g: CGFloat = 0.0
|
|
|
+ var b: CGFloat = 0.0
|
|
|
+ var a: CGFloat = 1.0
|
|
|
+
|
|
|
+ let length = hexSanitized.count
|
|
|
+
|
|
|
+ Scanner(string: hexSanitized).scanHexInt64(&rgb)
|
|
|
+
|
|
|
+ if length == 6 {
|
|
|
+ r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
|
|
|
+ g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
|
|
|
+ b = CGFloat(rgb & 0x0000FF) / 255.0
|
|
|
+ } else if length == 8 {
|
|
|
+ r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
|
|
|
+ g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
|
|
|
+ b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
|
|
|
+ a = CGFloat(rgb & 0x000000FF) / 255.0
|
|
|
+ }
|
|
|
+
|
|
|
+ self.init(.sRGB,
|
|
|
+ red: Double(r),
|
|
|
+ green: Double(g),
|
|
|
+ blue: Double(b),
|
|
|
+ opacity: Double(a))
|
|
|
+ }
|
|
|
+
|
|
|
+ public init(from decoder: Decoder) throws {
|
|
|
+ let container = try decoder.singleValueContainer()
|
|
|
+ let hex = try container.decode(String.self)
|
|
|
+
|
|
|
+ self.init(hex: hex)
|
|
|
+ }
|
|
|
+
|
|
|
+ public func encode(to encoder: Encoder) throws {
|
|
|
+ var container = encoder.singleValueContainer()
|
|
|
+ try container.encode(hex)
|
|
|
+ }
|
|
|
+
|
|
|
+ var hex: String? {
|
|
|
+ return toHex()
|
|
|
+ }
|
|
|
+
|
|
|
+ func toHex(alpha: Bool = false) -> String? {
|
|
|
+ guard let components = UnifiedColor(self).cgColor.components, components.count >= 3 else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ let r = Float(components[0])
|
|
|
+ let g = Float(components[1])
|
|
|
+ let b = Float(components[2])
|
|
|
+ var a = Float(1.0)
|
|
|
+
|
|
|
+ if components.count >= 4 {
|
|
|
+ a = Float(components[3])
|
|
|
+ }
|
|
|
+
|
|
|
+ if alpha {
|
|
|
+ return String(format: "%02lX%02lX%02lX%02lX",
|
|
|
+ lroundf(r * 255),
|
|
|
+ lroundf(g * 255),
|
|
|
+ lroundf(b * 255),
|
|
|
+ lroundf(a * 255))
|
|
|
+ } else {
|
|
|
+ return String(format: "%02lX%02lX%02lX",
|
|
|
+ lroundf(r * 255),
|
|
|
+ lroundf(g * 255),
|
|
|
+ lroundf(b * 255))
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+final class Group : Identifiable, Codable {
|
|
|
+ var name: String = ""
|
|
|
+ var color: Color = Color(.blue)
|
|
|
+
|
|
|
+ var id: String { name }
|
|
|
+
|
|
|
+ init() {}
|
|
|
+
|
|
|
+ init(name: String, color: Color) {
|
|
|
+ self.name = name
|
|
|
+ self.color = color
|
|
|
+ }
|
|
|
+
|
|
|
+ var valid: Bool { !name.isEmpty }
|
|
|
+}
|