mirror of
https://github.com/neon443/StickerSlack.git
synced 2026-03-11 13:26:17 +00:00
186 lines
4.6 KiB
Swift
186 lines
4.6 KiB
Swift
//
|
|
// EmojiHoarder.swift
|
|
// StickerSlack
|
|
//
|
|
// Created by neon443 on 15/10/2025.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
import Combine
|
|
import UniformTypeIdentifiers
|
|
import Haptics
|
|
|
|
class EmojiHoarder: ObservableObject {
|
|
static let shared: EmojiHoarder = EmojiHoarder()
|
|
static let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.neon443.StickerSlack")!.appendingPathComponent("Library", conformingTo: .directory)
|
|
nonisolated static let localEmojiDB: URL = EmojiHoarder.container.appendingPathComponent("_localEmojiDB.json", conformingTo: .fileURL)
|
|
private let endpoint: URL = URL(string: "https://cachet.dunkirk.sh/emojis")!
|
|
private let encoder = JSONEncoder()
|
|
private let decoder = JSONDecoder()
|
|
|
|
@Published var emojis: [Emoji] = []
|
|
|
|
@Published var trie: Trie = Trie()
|
|
@Published var filteredEmojis: [String] = []
|
|
@Published var downloadedEmojis: Set<String> = []
|
|
@Published var searchTerm: String = ""
|
|
|
|
init(localOnly: Bool = false, skipIndex: Bool = false) {
|
|
let localDB = loadLocalDB()
|
|
withAnimation { self.emojis = localDB }
|
|
if !skipIndex { buildTrie() }
|
|
withAnimation { self.filteredEmojis = [] }
|
|
|
|
guard !localOnly else { return }
|
|
Task.detached {
|
|
print("start loading remote db")
|
|
await self.loadRemoteDB()
|
|
print("end")
|
|
if !skipIndex { await self.buildTrie() }
|
|
}
|
|
}
|
|
|
|
func storeStickers(_ toStore: [UUID]) {
|
|
for stickerId in toStore {
|
|
print(stickerId)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
func deleteAllStickers() {
|
|
for i in emojis.indices {
|
|
guard downloadedEmojis.contains(emojis[i].name) else { continue }
|
|
delete(emoji: emojis[i])
|
|
}
|
|
}
|
|
|
|
func storeDB() {
|
|
try! encoder.encode(emojis).write(to: EmojiHoarder.localEmojiDB)
|
|
}
|
|
|
|
func storeDB(data: Data) {
|
|
try! data.write(to: EmojiHoarder.localEmojiDB)
|
|
}
|
|
|
|
func resetTrie() {
|
|
trie.root = TrieNode()
|
|
trie.dict = [:]
|
|
downloadedEmojis = []
|
|
filteredEmojis = []
|
|
}
|
|
|
|
func buildTrie() {
|
|
let start = Date().timeIntervalSince1970
|
|
trie.root = TrieNode()
|
|
for emoji in emojis {
|
|
trie.insert(word: emoji.name, emoji: emoji)
|
|
}
|
|
buildTrieDict()
|
|
print("done building trie in", Date().timeIntervalSince1970-start)
|
|
}
|
|
|
|
func buildTrieDict() {
|
|
var dict: [String:Emoji] = [:]
|
|
for emoji in emojis {
|
|
dict[emoji.name] = emoji
|
|
}
|
|
self.trie.dict = dict
|
|
buildDownloadedEmojis()
|
|
}
|
|
|
|
func buildDownloadedEmojis() {
|
|
downloadedEmojis = []
|
|
for emoji in emojis {
|
|
guard emoji.isLocal else { continue }
|
|
downloadedEmojis.insert(emoji.name)
|
|
}
|
|
}
|
|
|
|
nonisolated
|
|
func loadLocalDB() -> [Emoji] {
|
|
if let localEmojiDB = try? Data(contentsOf: EmojiHoarder.localEmojiDB) {
|
|
let decoded = try! decoder.decode([Emoji].self, from: localEmojiDB)
|
|
return decoded
|
|
}
|
|
return []
|
|
}
|
|
|
|
func loadRemoteDB() async {
|
|
async let fetched = self.fetchRemoteDB()
|
|
if let fetched = await fetched {
|
|
withAnimation { self.emojis = fetched }
|
|
}
|
|
}
|
|
|
|
nonisolated
|
|
func fetchRemoteDB() async -> [Emoji]? {
|
|
do {
|
|
async let (data, _) = try URLSession.shared.data(from: endpoint)
|
|
decoder.dateDecodingStrategy = .iso8601
|
|
let decoded: [SlackResponse] = try decoder.decode([SlackResponse].self, from: await data)
|
|
try await storeDB(data: await data)
|
|
return await SlackResponse.toEmojis(from: decoded)
|
|
} catch {
|
|
print(error.localizedDescription)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func refreshDB() async {
|
|
guard let fetched = await self.fetchRemoteDB() else {
|
|
let local = loadLocalDB()
|
|
await MainActor.run {
|
|
emojis = local
|
|
buildTrie()
|
|
}
|
|
return
|
|
}
|
|
await MainActor.run {
|
|
withAnimation { self.emojis = fetched }
|
|
buildTrie()
|
|
}
|
|
}
|
|
|
|
func filterEmojis(by searchTerm: String) {
|
|
withAnimation { filteredEmojis = trie.search(prefix: searchTerm) }
|
|
}
|
|
|
|
func download(emoji: Emoji) {
|
|
Task.detached {
|
|
try? await emoji.downloadImage()
|
|
await MainActor.run {
|
|
self.downloadedEmojis.insert(emoji.name)
|
|
self.trie.dict[emoji.name]?.refresh()
|
|
Haptic.success.trigger()
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
func delete(emoji: Emoji) {
|
|
emoji.deleteImage()
|
|
downloadedEmojis.remove(emoji.name)
|
|
self.trie.dict[emoji.name]?.refresh()
|
|
Haptic.heavy.trigger()
|
|
}
|
|
|
|
// 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 } }
|
|
// }
|
|
// }
|
|
// }
|
|
}
|