Files
StickerSlack/StickerSlack/EmojiHoarder.swift
neon443 bee0853f97 okay!
this is not good...
2025-11-02 22:25:01 +00:00

169 lines
4.6 KiB
Swift

//
// EmojiHoarder.swift
// StickerSlack
//
// Created by neon443 on 15/10/2025.
//
import Foundation
import SwiftUI
import Combine
import UniformTypeIdentifiers
class EmojiHoarder: ObservableObject {
static let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.neon443.StickerSlack")!.appendingPathComponent("Library", conformingTo: .directory)
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 localEmojis: Set<Emoji> = []
@Published var filteredEmojis: [Emoji] = []
@Published var prefix: Int = 100
init(localOnly: Bool = false) {
let localDB = loadLocalDB()
withAnimation { self.emojis = localDB }
withAnimation { self.filteredEmojis = localDB }
guard !localOnly else { return }
Task.detached {
print("start loading remote db")
await self.loadRemoteDB()
print("start local emojis loading")
await self.findLocalEmojis()
print("end")
}
}
func storeStickers(_ toStore: [UUID]) {
for stickerId in toStore {
print(stickerId)
}
}
func deleteAllStickers() async {
await withTaskGroup { group in
for i in emojis.indices {
group.addTask {
guard await self.localEmojis.contains(self.emojis[i]) else { return }
await self.emojis[i].deleteImage()
DispatchQueue.main.sync {
self.emojis[i].refresh()
}
}
}
}
}
nonisolated
func findLocalEmojis() async {
let filteredSetted = await Set(self.emojis.filter { $0.isLocal })
await MainActor.run {
self.localEmojis = filteredSetted
}
return
}
func storeDB() {
try! encoder.encode(emojis).write(to: EmojiHoarder.localEmojiDB)
}
func storeDB(data: Data) {
try! data.write(to: EmojiHoarder.localEmojiDB)
}
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 }
withAnimation { self.filteredEmojis = 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 { 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) }
}
}
}
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 { self.localEmojis.contains($0) } }
case .notDownloaded:
withAnimation(.interactiveSpring) { self.filteredEmojis = self.filteredEmojis.filter { !self.localEmojis.contains($0) } }
}
}
}
func downloadEmoji(_ toDownload: Emoji) {
Task.detached {
if (try? await toDownload.downloadImage()) != nil {
let index = await self.emojis.firstIndex { $0 == toDownload }
guard let index else { return }
await MainActor.run {
self.localEmojis.insert(toDownload)
self.emojis[index].refresh()
}
}
}
}
@MainActor
func deleteEmoji(_ toDelete: Emoji) {
toDelete.deleteImage()
let index = self.emojis.firstIndex { $0 == toDelete }
guard let index else { return }
self.localEmojis.remove(toDelete)
self.emojis[index].refresh()
}
}