Category.swift 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. //
  2. // Category.swift
  3. // Todos
  4. //
  5. // Created by Sam Jaffe on 3/2/26.
  6. //
  7. import Foundation
  8. import SwiftUI
  9. // https://gist.github.com/peterfriese/bb2fc5df202f6a15cc807bd87ff15193
  10. // Inspired by https://cocoacasts.com/from-hex-to-uicolor-and-back-in-swift
  11. // Make Color codable. This includes support for transparency.
  12. // See https://www.digitalocean.com/community/tutorials/css-hex-code-colors-alpha-values
  13. extension Color: @retroactive Codable {
  14. #if os(macOS)
  15. fileprivate typealias UnifiedColor = NSColor
  16. #endif
  17. #if os(iOS)
  18. fileprivate typealias UnifiedColor = UIColor
  19. #endif
  20. init(hex: String) {
  21. var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
  22. hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
  23. var rgb: UInt64 = 0
  24. var red: CGFloat = 0.0
  25. var green: CGFloat = 0.0
  26. var blue: CGFloat = 0.0
  27. var opacity: CGFloat = 1.0
  28. let length = hexSanitized.count
  29. Scanner(string: hexSanitized).scanHexInt64(&rgb)
  30. if length == 6 {
  31. red = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
  32. green = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
  33. blue = CGFloat(rgb & 0x0000FF) / 255.0
  34. } else if length == 8 {
  35. red = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
  36. green = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
  37. blue = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
  38. opacity = CGFloat(rgb & 0x000000FF) / 255.0
  39. }
  40. self.init(.sRGB,
  41. red: Double(red),
  42. green: Double(green),
  43. blue: Double(blue),
  44. opacity: Double(opacity))
  45. }
  46. public init(from decoder: Decoder) throws {
  47. let container = try decoder.singleValueContainer()
  48. let hex = try container.decode(String.self)
  49. self.init(hex: hex)
  50. }
  51. public func encode(to encoder: Encoder) throws {
  52. var container = encoder.singleValueContainer()
  53. try container.encode(hex)
  54. }
  55. var hex: String? {
  56. return toHex()
  57. }
  58. func toHex(alpha: Bool = false) -> String? {
  59. guard let components = UnifiedColor(self).cgColor.components, components.count >= 3 else {
  60. return nil
  61. }
  62. let red = Float(components[0])
  63. let green = Float(components[1])
  64. let blue = Float(components[2])
  65. var opacity = Float(1.0)
  66. if components.count >= 4 {
  67. opacity = Float(components[3])
  68. }
  69. if alpha {
  70. return String(format: "%02lX%02lX%02lX%02lX",
  71. lroundf(red * 255),
  72. lroundf(green * 255),
  73. lroundf(blue * 255),
  74. lroundf(opacity * 255))
  75. } else {
  76. return String(format: "%02lX%02lX%02lX",
  77. lroundf(red * 255),
  78. lroundf(green * 255),
  79. lroundf(blue * 255))
  80. }
  81. }
  82. }
  83. final class Category: Identifiable, Codable {
  84. var name: String = ""
  85. var color: Color = Color(.gray)
  86. var id: String { name }
  87. init() {}
  88. init(name: String, color: Color) {
  89. self.name = name
  90. self.color = color
  91. }
  92. var valid: Bool { !name.isEmpty }
  93. }