krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.7.0: Hutch/Views/Projects/ProjectPinStore.swift · raw
1import Foundation
2
3enum ProjectPinStore {
4 static func loadPinnedProjectIDs(
5 for userKey: String,
6 defaults: UserDefaults = .standard
7 ) -> [String] {
8 let generalizedPins = HomePinStore.pinnedProjectIDs(for: userKey, defaults: defaults)
9 if !generalizedPins.isEmpty {
10 return generalizedPins
11 }
12
13 let pinnedProjects = loadAll(defaults: defaults)
14 return normalizedProjectIDs(pinnedProjects[userKey] ?? [])
15 }
16
17 static func isPinned(
18 projectID: String,
19 for userKey: String,
20 defaults: UserDefaults = .standard
21 ) -> Bool {
22 loadPinnedProjectIDs(for: userKey, defaults: defaults).contains(projectID)
23 }
24
25 static func togglePin(
26 projectID: String,
27 for userKey: String,
28 defaults: UserDefaults = .standard
29 ) {
30 let trimmed = projectID.trimmingCharacters(in: .whitespacesAndNewlines)
31 guard !trimmed.isEmpty else { return }
32
33 HomePinStore.togglePin(
34 HomePinRecord(
35 kind: .project,
36 value: trimmed,
37 title: "Pinned Project",
38 subtitle: "Project",
39 ownerUsername: nil,
40 service: nil
41 ),
42 for: userKey,
43 defaults: defaults
44 )
45
46 var pinnedProjects = loadAll(defaults: defaults)
47 var projectIDs = normalizedProjectIDs(pinnedProjects[userKey] ?? [])
48
49 if let index = projectIDs.firstIndex(of: projectID) {
50 projectIDs.remove(at: index)
51 } else {
52 if let normalizedProjectID = normalizedProjectID(projectID) {
53 projectIDs.append(normalizedProjectID)
54 }
55 }
56
57 pinnedProjects[userKey] = projectIDs
58 save(pinnedProjects, defaults: defaults)
59 }
60
61 static func loadLegacyPinnedProjectIDs(
62 for userKey: String,
63 defaults: UserDefaults = .standard
64 ) -> [String] {
65 let pinnedProjects = loadAll(defaults: defaults)
66 return normalizedProjectIDs(pinnedProjects[userKey] ?? [])
67 }
68
69 private static func loadAll(defaults: UserDefaults) -> [String: [String]] {
70 guard let data = defaults.data(forKey: AppStorageKeys.pinnedHomeProjects) else {
71 return [:]
72 }
73
74 let decoded = (try? JSONDecoder().decode([String: [String]].self, from: data)) ?? [:]
75 return decoded.reduce(into: [String: [String]]()) { result, entry in
76 let normalizedUserKey = entry.key.trimmingCharacters(in: .whitespacesAndNewlines)
77 guard !normalizedUserKey.isEmpty else { return }
78 let normalizedProjectIDs = normalizedProjectIDs(entry.value)
79 if !normalizedProjectIDs.isEmpty {
80 result[normalizedUserKey] = normalizedProjectIDs
81 }
82 }
83 }
84
85 private static func save(_ pinnedProjects: [String: [String]], defaults: UserDefaults) {
86 guard let data = try? JSONEncoder().encode(pinnedProjects) else { return }
87 defaults.set(data, forKey: AppStorageKeys.pinnedHomeProjects)
88 }
89
90 private static func normalizedProjectIDs(_ projectIDs: [String]) -> [String] {
91 var seen = Set<String>()
92 return projectIDs.compactMap { projectID in
93 guard let normalizedProjectID = normalizedProjectID(projectID) else { return nil }
94 guard seen.insert(normalizedProjectID).inserted else { return nil }
95 return normalizedProjectID
96 }
97 }
98
99 private static func normalizedProjectID(_ projectID: String) -> String? {
100 let trimmed = projectID.trimmingCharacters(in: .whitespacesAndNewlines)
101 return trimmed.isEmpty ? nil : trimmed
102 }
103}