a lot of updates

grid view for downloaded emojis

shared instance of EmojiHoarder
nonisolated static localEmojiDB var
deleteallstickers is @MainActor
This commit is contained in:
neon443
2025-11-06 20:25:40 +00:00
parent 42739c93da
commit 7dcf04f27e
8 changed files with 75 additions and 45 deletions

View File

@@ -14,9 +14,9 @@
A9104C7C2EB3AE6300D160EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A949B1EF2EA04E8200215164 /* Assets.xcassets */; }; A9104C7C2EB3AE6300D160EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A949B1EF2EA04E8200215164 /* Assets.xcassets */; };
A9104C7F2EB4022500D160EA /* MSSticker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9104C7D2EB4022500D160EA /* MSSticker.swift */; }; A9104C7F2EB4022500D160EA /* MSSticker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9104C7D2EB4022500D160EA /* MSSticker.swift */; };
A9104C802EB4022500D160EA /* MSSticker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9104C7D2EB4022500D160EA /* MSSticker.swift */; }; A9104C802EB4022500D160EA /* MSSticker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9104C7D2EB4022500D160EA /* MSSticker.swift */; };
A9112EAC2EAFFDB0006739E2 /* Haptics in Frameworks */ = {isa = PBXBuildFile; productRef = A9112EAB2EAFFDB0006739E2 /* Haptics */; };
A924C3732EA9127200F20781 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = A924C3712EA9127200F20781 /* Emoji.swift */; }; A924C3732EA9127200F20781 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = A924C3712EA9127200F20781 /* Emoji.swift */; };
A924C3782EA9225800F20781 /* Haptics in Frameworks */ = {isa = PBXBuildFile; productRef = A924C3772EA9225800F20781 /* Haptics */; }; A924C3782EA9225800F20781 /* Haptics in Frameworks */ = {isa = PBXBuildFile; productRef = A924C3772EA9225800F20781 /* Haptics */; };
A931D4082EBC9646007BC75B /* Haptics in Frameworks */ = {isa = PBXBuildFile; productRef = A931D4072EBC9646007BC75B /* Haptics */; };
A935437B2EB2A3C800BB80A4 /* FilterCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A935437A2EB2A3C800BB80A4 /* FilterCategory.swift */; }; A935437B2EB2A3C800BB80A4 /* FilterCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A935437A2EB2A3C800BB80A4 /* FilterCategory.swift */; };
A949B1F32EA04E8200215164 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A949B1EF2EA04E8200215164 /* Assets.xcassets */; }; A949B1F32EA04E8200215164 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A949B1EF2EA04E8200215164 /* Assets.xcassets */; };
A949B1F42EA04E8200215164 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949B1F02EA04E8200215164 /* ContentView.swift */; }; A949B1F42EA04E8200215164 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949B1F02EA04E8200215164 /* ContentView.swift */; };
@@ -126,7 +126,6 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A924C3782EA9225800F20781 /* Haptics in Frameworks */, A924C3782EA9225800F20781 /* Haptics in Frameworks */,
A9112EAC2EAFFDB0006739E2 /* Haptics in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -135,6 +134,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A986A6AE2EB658DF00B6E0FA /* Messages.framework in Frameworks */, A986A6AE2EB658DF00B6E0FA /* Messages.framework in Frameworks */,
A931D4082EBC9646007BC75B /* Haptics in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -337,6 +337,7 @@
); );
name = StickerSlackiMessageApp; name = StickerSlackiMessageApp;
packageProductDependencies = ( packageProductDependencies = (
A931D4072EBC9646007BC75B /* Haptics */,
); );
productName = StickerSlackiMessageApp; productName = StickerSlackiMessageApp;
productReference = A986A6AD2EB658DF00B6E0FA /* StickerSlackiMessageApp.appex */; productReference = A986A6AD2EB658DF00B6E0FA /* StickerSlackiMessageApp.appex */;
@@ -909,6 +910,11 @@
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = Haptics; productName = Haptics;
}; };
A931D4072EBC9646007BC75B /* Haptics */ = {
isa = XCSwiftPackageProductDependency;
package = A9112EAA2EAFFDB0006739E2 /* XCRemoteSwiftPackageReference "Haptics" */;
productName = Haptics;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };
rootObject = A949B1D72EA04C0B00215164 /* Project object */; rootObject = A949B1D72EA04C0B00215164 /* Project object */;

View File

@@ -12,8 +12,9 @@ import UniformTypeIdentifiers
import Haptics import Haptics
class EmojiHoarder: ObservableObject { class EmojiHoarder: ObservableObject {
static let shared: EmojiHoarder = EmojiHoarder()
static let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.neon443.StickerSlack")!.appendingPathComponent("Library", conformingTo: .directory) 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) 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 endpoint: URL = URL(string: "https://cachet.dunkirk.sh/emojis")!
private let encoder = JSONEncoder() private let encoder = JSONEncoder()
private let decoder = JSONDecoder() private let decoder = JSONDecoder()
@@ -46,6 +47,7 @@ class EmojiHoarder: ObservableObject {
} }
} }
@MainActor
func deleteAllStickers() { func deleteAllStickers() {
for i in emojis.indices { for i in emojis.indices {
guard downloadedEmojis.contains(emojis[i].name) else { continue } guard downloadedEmojis.contains(emojis[i].name) else { continue }

View File

@@ -9,20 +9,55 @@ import SwiftUI
import Haptics import Haptics
struct ContentView: View { struct ContentView: View {
@StateObject var hoarder: EmojiHoarder = EmojiHoarder() @StateObject var hoarder: EmojiHoarder = .shared
@State var searchTerm: String = "" @State var searchTerm: String = ""
var col: GridItem = GridItem(.fixed(100), spacing: 0, alignment: .center) var minColWidth: CGFloat { 75 }
var spacing: CGFloat { 10 }
var col: GridItem {
GridItem(
.flexible(minimum: minColWidth, maximum: 125),
spacing: spacing,
alignment: .center
)
}
@Environment(\.colorScheme) var colorScheme
var isDark: Bool { colorScheme == .dark }
var body: some View { var body: some View {
TabView { TabView {
LazyHGrid(rows: Array(repeating: col, count: 4), spacing: 10) { ScrollView {
ForEach(hoarder.downloadedEmojis.sorted(by: <), id: \.self) { name in let columns: Int = max(1, Int((UIScreen.main.bounds.width - 2*spacing) / (minColWidth + spacing)))
if let emoji = hoarder.trie.dict[name] { let layout = Array(repeating: col, count: columns)
EmojiPreview(hoarder: hoarder, emoji: emoji) LazyVGrid(columns: layout, spacing: 10) {
ForEach(hoarder.emojis, id: \.self) { emoji in
if hoarder.downloadedEmojis.contains(emoji.name) {
ZStack {
Rectangle()
.foregroundStyle(isDark ? .black : .white)
EmojiPreview(emoji: emoji)
RoundedRectangle(cornerRadius: 15)
.stroke(.gray, lineWidth: 1)
}
.aspectRatio(1, contentMode: .fit) .aspectRatio(1, contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 15))
.contextMenu {
Text(emoji.name)
Button("Share", systemImage: "square.and.arrow.up") {
}
ShareLink("Share", item: emoji.localImageURL, subject: nil, message: nil)
ShareLink("Share", item: emoji.remoteImageURL, subject: nil, message: nil)
Divider()
Button("Delete", systemImage: "trash.fill", role: .destructive) {
hoarder.delete(emoji: emoji)
}
}
}
} }
} }
.padding(.horizontal, spacing)
} }
.tabItem { .tabItem {
Label("Downloaded", systemImage: "arrow.down.circle.fill") Label("Downloaded", systemImage: "arrow.down.circle.fill")
@@ -30,7 +65,7 @@ struct ContentView: View {
List { List {
ForEach(hoarder.emojis, id: \.self) { emoji in ForEach(hoarder.emojis, id: \.self) { emoji in
EmojiRow(hoarder: hoarder, emoji: emoji) EmojiRow(emoji: emoji)
} }
} }
.tabItem { .tabItem {
@@ -42,7 +77,7 @@ struct ContentView: View {
ForEach(hoarder.filteredEmojis, id: \.self) { name in ForEach(hoarder.filteredEmojis, id: \.self) { name in
if let emoji = hoarder.trie.dict[name] { if let emoji = hoarder.trie.dict[name] {
EmojiRow(hoarder: hoarder, emoji: emoji) EmojiRow(emoji: emoji)
} }
} }
} }
@@ -59,12 +94,10 @@ struct ContentView: View {
Label("Search", systemImage: "magnifyingglass") Label("Search", systemImage: "magnifyingglass")
} }
TrieTestingView( TrieTestingView()
hoarder: hoarder, .tabItem {
) Label("Tree", systemImage: "tree.fill")
.tabItem { }
Label("Tree", systemImage: "tree.fill")
}
} }
.searchable(text: $searchTerm, placement: .automatic) .searchable(text: $searchTerm, placement: .automatic)
} }

View File

@@ -9,7 +9,7 @@ import SwiftUI
import Haptics import Haptics
struct EmojiPreview: View { struct EmojiPreview: View {
@ObservedObject var hoarder: EmojiHoarder @ObservedObject var hoarder: EmojiHoarder = .shared
@State var emoji: Emoji @State var emoji: Emoji
@State private var id: UUID = UUID() @State private var id: UUID = UUID()
@@ -20,13 +20,6 @@ struct EmojiPreview: View {
if let image = emoji.image { if let image = emoji.image {
Image(uiImage: image) Image(uiImage: image)
.resizable().scaledToFit() .resizable().scaledToFit()
.border(.orange)
.overlay(alignment: .bottomLeading) {
Image(systemName: "arrow.down.circle.fill")
.foregroundStyle(.gray)
.shadow(radius: 1)
.symbolRenderingMode(.hierarchical)
}
} else { } else {
AsyncImage(url: emoji.remoteImageURL) { phase in AsyncImage(url: emoji.remoteImageURL) { phase in
if let image = phase.image { if let image = phase.image {
@@ -54,13 +47,6 @@ struct EmojiPreview: View {
} }
} }
#Preview {
EmojiPreview(
hoarder: EmojiHoarder(localOnly: true),
emoji: Emoji.test
)
}
struct ImageErrorView: View { struct ImageErrorView: View {
var body: some View { var body: some View {
Image(systemName: "xmark.app.fill") Image(systemName: "xmark.app.fill")
@@ -70,3 +56,10 @@ struct ImageErrorView: View {
.foregroundStyle(.red) .foregroundStyle(.red)
} }
} }
#Preview {
EmojiPreview(
hoarder: EmojiHoarder(localOnly: true),
emoji: Emoji.test
)
}

View File

@@ -9,7 +9,7 @@ import SwiftUI
import Haptics import Haptics
struct EmojiRow: View { struct EmojiRow: View {
@ObservedObject var hoarder: EmojiHoarder @ObservedObject var hoarder: EmojiHoarder = .shared
@State var emoji: Emoji @State var emoji: Emoji
var body: some View { var body: some View {
@@ -19,10 +19,7 @@ struct EmojiRow: View {
// Text // Text
Text(emoji.name) Text(emoji.name)
} }
EmojiPreview( EmojiPreview(emoji: emoji)
hoarder: hoarder,
emoji: emoji
)
} }
.frame(maxWidth: 100, maxHeight: 100) .frame(maxWidth: 100, maxHeight: 100)
Spacer() Spacer()

View File

@@ -64,11 +64,11 @@ struct TrieTestingView: View {
} }
if uikit { if uikit {
EmojiCollectionView(hoarder: hoarder, items: filterResult) EmojiCollectionView(items: filterResult)
.id(filterResult) .id(filterResult)
} else { } else {
List(filterResult, id: \.self) { item in List(filterResult, id: \.self) { item in
EmojiRow(hoarder: hoarder, emoji: hoarder.trie.dict[item]!) EmojiRow(emoji: hoarder.trie.dict[item]!)
} }
} }

View File

@@ -11,7 +11,7 @@ import SwiftUI
import Haptics import Haptics
struct EmojiCollectionView: UIViewRepresentable { struct EmojiCollectionView: UIViewRepresentable {
let hoarder: EmojiHoarder let hoarder: EmojiHoarder = .shared
let items: [String] let items: [String]
func makeUIView(context: Context) -> UITableView { func makeUIView(context: Context) -> UITableView {
@@ -28,15 +28,14 @@ struct EmojiCollectionView: UIViewRepresentable {
} }
func makeCoordinator() -> Coordinator { func makeCoordinator() -> Coordinator {
Coordinator(hoarder: hoarder, items: items) Coordinator(items: items)
} }
final class Coordinator: NSObject, UITableViewDataSource { final class Coordinator: NSObject, UITableViewDataSource {
var hoarder: EmojiHoarder var hoarder: EmojiHoarder = .shared
var items: [String] var items: [String]
init(hoarder: EmojiHoarder, items: [String]) { init(items: [String]) {
self.hoarder = hoarder
self.items = items self.items = items
} }

View File

@@ -31,7 +31,7 @@ struct StickerSlackTests {
} }
@Test func deleteAllEmojis() async throws { @Test func deleteAllEmojis() async throws {
let performanceTests = PerformanceTests(hoarder: hoarder) let performanceTests = PerformanceTests(hoarder: .shared)
try! await performanceTests.fakeDownloadAllStickers() try! await performanceTests.fakeDownloadAllStickers()
await hoarder.deleteAllStickers() await hoarder.deleteAllStickers()
} }