diff --git a/StickerSlack/Emoji/Trie.swift b/StickerSlack/Emoji/Trie.swift index 1fbfbc4..aa9bbb6 100644 --- a/StickerSlack/Emoji/Trie.swift +++ b/StickerSlack/Emoji/Trie.swift @@ -93,11 +93,15 @@ struct TrieTestingView: View { var body: some View { VStack { + Button("reset", role: .destructive) { + trie.root = TrieNode() + } Button("add emojis!") { - for name in hoarder.emojis.map({ $0.name }) { - trie.insert(word: name) + let start = Date().timeIntervalSince1970 + for emoji in hoarder.emojis { + trie.insert(word: emoji.name) } - print("done!") + print("done!", Date().timeIntervalSince1970-start) } .buttonStyle(.borderedProminent) diff --git a/StickerSlack/EmojiHoarder.swift b/StickerSlack/EmojiHoarder.swift index e2e515e..7b5108f 100644 --- a/StickerSlack/EmojiHoarder.swift +++ b/StickerSlack/EmojiHoarder.swift @@ -18,19 +18,22 @@ class EmojiHoarder: ObservableObject { private let decoder = JSONDecoder() @Published var emojis: [Emoji] = [] - @Published var filteredEmojis: [Emoji] = [] + @Published var trie: Trie = Trie() + @Published var filteredEmojis: [String] = [] @Published var prefix: Int = 100 init(localOnly: Bool = false) { let localDB = loadLocalDB() withAnimation { self.emojis = localDB } - withAnimation { self.filteredEmojis = localDB } + buildTrie() + withAnimation { self.filteredEmojis = [] } guard !localOnly else { return } Task.detached { print("start loading remote db") await self.loadRemoteDB() print("end") + await self.buildTrie() } } @@ -62,6 +65,14 @@ class EmojiHoarder: ObservableObject { try! data.write(to: EmojiHoarder.localEmojiDB) } + func buildTrie() { + let start = Date().timeIntervalSince1970 + for emoji in emojis { + trie.insert(word: emoji.name) + } + print("done building trie in", Date().timeIntervalSince1970-start) + } + nonisolated func loadLocalDB() -> [Emoji] { if let localEmojiDB = try? Data(contentsOf: EmojiHoarder.localEmojiDB) { @@ -75,7 +86,6 @@ class EmojiHoarder: ObservableObject { async let fetched = self.fetchRemoteDB() if let fetched = await fetched { withAnimation { self.emojis = fetched } - withAnimation { self.filteredEmojis = fetched } } } @@ -97,38 +107,28 @@ class EmojiHoarder: ObservableObject { guard let fetched = await self.fetchRemoteDB() else { return } DispatchQueue.main.async { withAnimation { self.emojis = fetched } - withAnimation { self.filteredEmojis = fetched } } } func filterEmojis(by searchTerm: String) { - guard !searchTerm.isEmpty else { - withAnimation(.interactiveSpring) { self.filteredEmojis = Array(emojis) } - return - } - Task.detached { - let filtered = await self.emojis.filter { $0.name.localizedCaseInsensitiveContains(searchTerm) } - DispatchQueue.main.async { - withAnimation(.interactiveSpring) { self.filteredEmojis = Array(filtered) } - } - } + filteredEmojis = trie.search(prefix: searchTerm) } - func filterEmojis(byCategory category: FilterCategory, searchTerm: String) { - guard category != .none else { - filterEmojis(by: searchTerm) - return - } - self.filterEmojis(by: searchTerm) - DispatchQueue.main.async { - switch category { - case .none: - fallthrough - case .downloaded: - withAnimation(.interactiveSpring) { self.filteredEmojis = self.filteredEmojis.filter { $0.isLocal } } - case .notDownloaded: - withAnimation(.interactiveSpring) { self.filteredEmojis = self.filteredEmojis.filter { !$0.isLocal } } - } - } - } +// func filterEmojis(byCategory category: FilterCategory, searchTerm: String) { +// guard category != .none else { +// filterEmojis(by: searchTerm) +// return +// } +// self.filterEmojis(by: searchTerm) +// DispatchQueue.main.async { +// switch category { +// case .none: +// fallthrough +// case .downloaded: +// withAnimation(.interactiveSpring) { self.filteredEmojis = self.filteredEmojis.filter { $0.isLocal } } +// case .notDownloaded: +// withAnimation(.interactiveSpring) { self.filteredEmojis = self.filteredEmojis.filter { !$0.isLocal } } +// } +// } +// } } diff --git a/StickerSlack/Views/ContentView.swift b/StickerSlack/Views/ContentView.swift index db7c2f6..91cba8a 100644 --- a/StickerSlack/Views/ContentView.swift +++ b/StickerSlack/Views/ContentView.swift @@ -22,17 +22,17 @@ struct ContentView: View { ) } - Button("none") { - hoarder.filterEmojis(byCategory: .none, searchTerm: searchTerm) - } - - Button("downloaded") { - hoarder.filterEmojis(byCategory: .downloaded, searchTerm: searchTerm) - } - - Button("not downloaded") { - hoarder.filterEmojis(byCategory: .notDownloaded, searchTerm: searchTerm) - } +// Button("none") { +// hoarder.filterEmojis(byCategory: .none, searchTerm: searchTerm) +// } +// +// Button("downloaded") { +// hoarder.filterEmojis(byCategory: .downloaded, searchTerm: searchTerm) +// } +// +// Button("not downloaded") { +// hoarder.filterEmojis(byCategory: .notDownloaded, searchTerm: searchTerm) +// } Button("delete all images") { Task.detached { @@ -42,52 +42,59 @@ struct ContentView: View { Text("\(hoarder.filteredEmojis.count) Emoji") - ForEach($hoarder.filteredEmojis, id: \.self) { $emoji in - HStack { - EmojiPreview( - hoarder: hoarder, - emoji: emoji - ) - .frame(maxWidth: 100, maxHeight: 100) - Spacer() - Button("", systemImage: "checkmark") { - if let sticker = emoji.sticker { - if sticker.validate() { - print("validation of \(emoji.name) succeeded") - Haptic.success.trigger() - } else { - print("validation of \(emoji.name) failed") - Haptic.error.trigger() - } - } - } - .buttonStyle(.plain) - if emoji.isLocal { - Button("", systemImage: "trash") { - emoji.deleteImage() - emoji.refresh() - } - .buttonStyle(.plain) - } else { - Button("", systemImage: "arrow.down.circle") { - Task.detached { - try? await emoji.downloadImage() - await MainActor.run { - emoji.refresh() + if searchTerm.isEmpty { + ForEach($hoarder.emojis, id: \.self) { $emoji in + HStack { + EmojiPreview( + hoarder: hoarder, + emoji: emoji + ) + .frame(maxWidth: 100, maxHeight: 100) + Spacer() + Button("", systemImage: "checkmark") { + if let sticker = emoji.sticker { + if sticker.validate() { + print("validation of \(emoji.name) succeeded") + Haptic.success.trigger() + } else { + print("validation of \(emoji.name) failed") + Haptic.error.trigger() } } } .buttonStyle(.plain) + if emoji.isLocal { + Button("", systemImage: "trash") { + emoji.deleteImage() + emoji.refresh() + } + .buttonStyle(.plain) + } else { + Button("", systemImage: "arrow.down.circle") { + Task.detached { + try? await emoji.downloadImage() + await MainActor.run { + emoji.refresh() + } + } + } + .buttonStyle(.plain) + } + } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + if emoji.isLocal { + Button("Remove", systemImage: "trash") { + emoji.deleteImage() + emoji.refresh() + } + .tint(.red) + } } } - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - if emoji.isLocal { - Button("Remove", systemImage: "trash") { - emoji.deleteImage() - emoji.refresh() - } - .tint(.red) - } + } else { + ForEach(hoarder.filteredEmojis, id: \.self) { name in + Text(name) +// EmojiPreview(hoarder: hoarder, emoji: hoarder.emojis.first!) } } }