krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.8.1: Hutch/Views/Search/ScopedSearchHistoryStore.swift · raw
1import Foundation
2
3struct ScopedSearchHistoryEntry: Codable, Hashable, Identifiable, Sendable {
4 let scopeID: String
5 let query: String
6 let createdAt: Date
7
8 var id: String {
9 "\(scopeID):\(query.lowercased())"
10 }
11}
12
13enum ScopedSearchHistoryStore {
14 private static let maximumEntriesPerScope = 8
15
16 static func load(scopeID: String, defaults: UserDefaults = .standard) -> [ScopedSearchHistoryEntry] {
17 loadAll(defaults: defaults)[scopeID] ?? []
18 }
19
20 static func record(
21 query: String,
22 scopeID: String,
23 defaults: UserDefaults = .standard,
24 now: Date = .now
25 ) {
26 let normalizedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
27 guard !normalizedQuery.isEmpty else { return }
28
29 var allEntries = loadAll(defaults: defaults)
30 var scopeEntries = allEntries[scopeID] ?? []
31 scopeEntries.removeAll {
32 $0.query.compare(normalizedQuery, options: [.caseInsensitive, .diacriticInsensitive]) == .orderedSame
33 }
34 scopeEntries.insert(
35 ScopedSearchHistoryEntry(scopeID: scopeID, query: normalizedQuery, createdAt: now),
36 at: 0
37 )
38 allEntries[scopeID] = Array(scopeEntries.prefix(maximumEntriesPerScope))
39 save(allEntries, defaults: defaults)
40 }
41
42 static func clear(scopeID: String, defaults: UserDefaults = .standard) {
43 var allEntries = loadAll(defaults: defaults)
44 allEntries.removeValue(forKey: scopeID)
45 save(allEntries, defaults: defaults)
46 }
47
48 private static func loadAll(defaults: UserDefaults) -> [String: [ScopedSearchHistoryEntry]] {
49 guard let data = defaults.data(forKey: AppStorageKeys.scopedSearchHistory) else {
50 return [:]
51 }
52
53 do {
54 return try JSONDecoder().decode([String: [ScopedSearchHistoryEntry]].self, from: data)
55 } catch {
56 defaults.removeObject(forKey: AppStorageKeys.scopedSearchHistory)
57 return [:]
58 }
59 }
60
61 private static func save(_ entries: [String: [ScopedSearchHistoryEntry]], defaults: UserDefaults) {
62 guard let data = try? JSONEncoder().encode(entries) else { return }
63 defaults.set(data, forKey: AppStorageKeys.scopedSearchHistory)
64 }
65}