1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import Foundation
struct RecentProfileSearch: Codable, Identifiable, Equatable {
let did: String
var displayName: String?
var handle: String?
var avatar: String?
var id: String {
did
}
}
struct RecentTextSearch: Codable, Identifiable, Equatable {
let query: String
var id: String {
query
}
}
@Observable
@MainActor
final class RecentSearchStorage {
var profiles: [RecentProfileSearch] = []
var textSearches: [RecentTextSearch] = []
private static let profilesKey = "recentSearchProfiles"
private static let textKey = "recentSearchText"
private static let maxProfiles = 10
private static let maxText = 10
/// Search history is per-account, so the keys carry the owning DID.
private let profilesKey: String
private let textKey: String
init(did: String? = AccountScopedStorage.activeAccountID) {
profilesKey = AccountScopedStorage.key(Self.profilesKey, did: did)
textKey = AccountScopedStorage.key(Self.textKey, did: did)
load()
}
func addProfile(did: String, displayName: String?, handle: String?, avatar: String?) {
profiles.removeAll { $0.did == did }
profiles.insert(RecentProfileSearch(did: did, displayName: displayName, handle: handle, avatar: avatar), at: 0)
if profiles.count > Self.maxProfiles {
profiles = Array(profiles.prefix(Self.maxProfiles))
}
save()
}
func addTextSearch(_ query: String) {
let trimmed = query.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
textSearches.removeAll { $0.query.lowercased() == trimmed.lowercased() }
textSearches.insert(RecentTextSearch(query: trimmed), at: 0)
if textSearches.count > Self.maxText {
textSearches = Array(textSearches.prefix(Self.maxText))
}
save()
}
func removeProfile(_ did: String) {
profiles.removeAll { $0.did == did }
save()
}
func removeTextSearch(_ query: String) {
textSearches.removeAll { $0.query == query }
save()
}
func clearAll() {
profiles = []
textSearches = []
save()
}
private func load() {
if let data = StorageEnvironment.defaults.data(forKey: profilesKey),
let decoded = try? JSONDecoder().decode([RecentProfileSearch].self, from: data)
{
profiles = decoded
}
if let data = StorageEnvironment.defaults.data(forKey: textKey),
let decoded = try? JSONDecoder().decode([RecentTextSearch].self, from: data)
{
textSearches = decoded
}
}
private func save() {
if let data = try? JSONEncoder().encode(profiles) {
StorageEnvironment.defaults.set(data, forKey: profilesKey)
}
if let data = try? JSONEncoder().encode(textSearches) {
StorageEnvironment.defaults.set(data, forKey: textKey)
}
}
}