Преглед изворни кода

feat: add definitions for Groups

Sam Jaffe пре 2 недеља
родитељ
комит
314f0162b9
3 измењених фајлова са 169 додато и 0 уклоњено
  1. 116 0
      Todos/Model/Group.swift
  2. 50 0
      Todos/View/Settings/CategoryGroupView.swift
  3. 3 0
      Todos/View/SettingsView.swift

+ 116 - 0
Todos/Model/Group.swift

@@ -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 }
+}

+ 50 - 0
Todos/View/Settings/CategoryGroupView.swift

@@ -0,0 +1,50 @@
+//
+//  CategoryGroupView.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/2/26.
+//
+
+import SwiftUI
+
+struct CategoryGroupView: View {
+  @State var allGroups = [Group]()
+  @State var active = Group()
+  
+  var body: some View {
+    Table(of: Binding<Group>.self) {
+      TableColumn("Color") { group in
+        ColorPicker("", selection: group.color, supportsOpacity: false)
+      }
+      TableColumn("Name") { group in
+        TextField("", text: group.name)
+          .onSubmit(addGroup)
+      }
+      TableColumn("") { group in
+        if group.id != active.id {
+          Button() {
+            allGroups.removeAll(where: { $0.id == group.id })
+          } label: {
+            Label("", systemImage: "trash")
+          }
+        }
+      }.width(max: 20)
+    } rows: {
+      ForEach($allGroups) { group in
+        TableRow(group)
+      }
+      TableRow($active)
+    }
+  }
+  
+  private func addGroup() {
+    if active.valid {
+      allGroups.append(active)
+      active = Group()
+    }
+  }
+}
+
+#Preview {
+  CategoryGroupView()
+}

+ 3 - 0
Todos/View/SettingsView.swift

@@ -10,6 +10,9 @@ import SwiftUI
 struct SettingsView: View {
   var body: some View {
     TabView {
+      Tab("Group", systemImage: "link") {
+        CategoryGroupView()
+      }
       Tab("URL Hints", systemImage: "link") {
         URLHintMappingView()
       }