diff --git a/Resources/image.png b/Resources/image.png new file mode 100644 index 0000000..93e2853 Binary files /dev/null and b/Resources/image.png differ diff --git a/StickerSlack.xcodeproj/project.pbxproj b/StickerSlack.xcodeproj/project.pbxproj index 68031c0..50af511 100644 --- a/StickerSlack.xcodeproj/project.pbxproj +++ b/StickerSlack.xcodeproj/project.pbxproj @@ -55,6 +55,9 @@ A986A6CE2EB659E000B6E0FA /* StickerBrowserDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A986A6CB2EB659E000B6E0FA /* StickerBrowserDataSource.swift */; }; A986A6CF2EB659E000B6E0FA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A986A6C62EB659E000B6E0FA /* Assets.xcassets */; }; A986A6D12EB659E000B6E0FA /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A986A6C92EB659E000B6E0FA /* MainInterface.storyboard */; }; + A99589AF2F5E0D83003A4A8C /* image.png in Resources */ = {isa = PBXBuildFile; fileRef = A99589AE2F5E0D83003A4A8C /* image.png */; }; + A99589B02F5E0D83003A4A8C /* image.png in Resources */ = {isa = PBXBuildFile; fileRef = A99589AE2F5E0D83003A4A8C /* image.png */; }; + A99589B12F5E0D83003A4A8C /* image.png in Resources */ = {isa = PBXBuildFile; fileRef = A99589AE2F5E0D83003A4A8C /* image.png */; }; A9B9A82E2EB2CCBE004C9245 /* StickerSlackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B9A82C2EB2CCBE004C9245 /* StickerSlackTests.swift */; }; A9B9A82F2EB2CCED004C9245 /* EmojiHoarder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949B1F72EA04F2300215164 /* EmojiHoarder.swift */; }; A9B9A8302EB2CD0B004C9245 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = A924C3712EA9127200F20781 /* Emoji.swift */; }; @@ -156,6 +159,7 @@ A986A6C82EB659E000B6E0FA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; A986A6CA2EB659E000B6E0FA /* MessagesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = ""; }; A986A6CB2EB659E000B6E0FA /* StickerBrowserDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerBrowserDataSource.swift; sourceTree = ""; }; + A99589AE2F5E0D83003A4A8C /* image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = image.png; sourceTree = ""; }; A9B9A8232EB2CCB5004C9245 /* StickerSlackTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StickerSlackTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A9B9A82C2EB2CCBE004C9245 /* StickerSlackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerSlackTests.swift; sourceTree = ""; }; A9BBC5172EB8FA4500FFE82F /* ViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = ""; }; @@ -206,6 +210,7 @@ A9104C742EB3AE4700D160EA /* Resources */ = { isa = PBXGroup; children = ( + A99589AE2F5E0D83003A4A8C /* image.png */, A9104C732EB3AE4700D160EA /* StickerSlack.icon */, A949B1EF2EA04E8200215164 /* Assets.xcassets */, A9104C722EB3AE4700D160EA /* Icon.pxd */, @@ -504,6 +509,7 @@ A9104C762EB3AE4700D160EA /* StickerSlack.icon in Resources */, A949B1F32EA04E8200215164 /* Assets.xcassets in Resources */, A9104C752EB3AE4700D160EA /* Icon.pxd in Resources */, + A99589AF2F5E0D83003A4A8C /* image.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -512,6 +518,7 @@ buildActionMask = 2147483647; files = ( A986A6CF2EB659E000B6E0FA /* Assets.xcassets in Resources */, + A99589B12F5E0D83003A4A8C /* image.png in Resources */, A986A6D12EB659E000B6E0FA /* MainInterface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -523,6 +530,7 @@ A9104C772EB3AE4700D160EA /* Icon.pxd in Resources */, A9104C7C2EB3AE6300D160EA /* Assets.xcassets in Resources */, A9104C782EB3AE4700D160EA /* StickerSlack.icon in Resources */, + A99589B02F5E0D83003A4A8C /* image.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/StickerSlack/Emoji/Emoji.swift b/StickerSlack/Emoji/Emoji.swift index 5b8c475..df0253f 100644 --- a/StickerSlack/Emoji/Emoji.swift +++ b/StickerSlack/Emoji/Emoji.swift @@ -13,7 +13,6 @@ import UniformTypeIdentifiers struct Emoji: StickerProtocol { var id: UUID - var uiID: UUID var name: String var localImageURLString: String { let urlString = remoteImageURL.absoluteString @@ -32,7 +31,6 @@ struct Emoji: StickerProtocol { init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(UUID.self, forKey: .id) - self.uiID = id self.name = try container.decode(String.self, forKey: .name) self.remoteImageURL = try container.decode(URL.self, forKey: .remoteImageURL) } @@ -43,37 +41,12 @@ struct Emoji: StickerProtocol { id: UUID = UUID() ) { self.id = id - self.uiID = id self.name = name self.remoteImageURL = url } - nonisolated - func downloadImage() async throws { - if let data = try? await Data(contentsOf: localImageURL), - let _ = UIImage(data: data) { - return - } - - var (data, _) = try await URLSession.shared.data(from: remoteImageURL) - - if let uiImage = UIImage(data: data), - let cgImage = uiImage.cgImage, - await !self.localImageURLString.contains(".gif"), - cgImage.width < 300 || cgImage.height < 300 { - data = await resize(image: uiImage, to: CGSize(width: 300, height: 300)).pngData()! - } - try! await data.write(to: localImageURL) - return - } - - @MainActor - mutating func refresh() { - withAnimation { self.uiID = UUID() } - } - static var test: Emoji = Emoji( - name: "s?", - url: URL(string: "https://files.catbox.moe/d8go4n.png")! + name: "a test slack emoji", + url: Bundle.main.url(forResource: "image", withExtension: "png")! ) } diff --git a/StickerSlack/Emoji/EmojiHoarder.swift b/StickerSlack/Emoji/EmojiHoarder.swift index 4470d37..1852867 100644 --- a/StickerSlack/Emoji/EmojiHoarder.swift +++ b/StickerSlack/Emoji/EmojiHoarder.swift @@ -37,7 +37,7 @@ class EmojiHoarder: ObservableObject { self.showWelcome = !UserDefaults.standard.bool(forKey: "showWelcome") let localDB = loadLocalDB() - withAnimation { self.emojis = localDB } + withAnimation(.snappy) { self.emojis = localDB } loadTrie() if !skipIndex { buildTrie() } @@ -204,7 +204,7 @@ class EmojiHoarder: ObservableObject { private func loadRemoteDB() async { async let fetched = self.fetchRemoteDB() if let fetched = await fetched { - withAnimation { self.emojis = fetched } + withAnimation(.snappy) { self.emojis = fetched } } } @@ -232,7 +232,7 @@ class EmojiHoarder: ObservableObject { return } await MainActor.run { - withAnimation { self.emojis = fetched } + withAnimation(.snappy) { self.emojis = fetched } buildTrie() } } @@ -241,11 +241,12 @@ class EmojiHoarder: ObservableObject { try? await emoji.downloadImage() await MainActor.run { if !skipStoreIndex { - self.downloadedEmojis.insert(emoji.name) - self.downloadedEmojisArr.append(emoji.name) + withAnimation(.snappy) { + self.downloadedEmojis.insert(emoji.name) + self.downloadedEmojisArr.append(emoji.name) + } self.storeDownloadedIndexes() } - self.trie.dict[emoji.name]?.refresh() if !skipStoreIndex { Haptic.success.trigger() } } } @@ -254,11 +255,12 @@ class EmojiHoarder: ObservableObject { func delete(emoji: Emoji, skipStoreIndex: Bool = false) { emoji.deleteImage() if !skipStoreIndex { - downloadedEmojis.remove(emoji.name) - downloadedEmojisArr.removeAll(where: { $0 == emoji.name }) + withAnimation(.snappy) { + downloadedEmojis.remove(emoji.name) + downloadedEmojisArr.removeAll(where: { $0 == emoji.name }) + } storeDownloadedIndexes() } - self.trie.dict[emoji.name]?.refresh() if !skipStoreIndex { Haptic.heavy.trigger() } } diff --git a/StickerSlack/Gifs/Gif.swift b/StickerSlack/Gifs/Gif.swift index 311cc3e..dedd85e 100644 --- a/StickerSlack/Gifs/Gif.swift +++ b/StickerSlack/Gifs/Gif.swift @@ -11,7 +11,6 @@ import UniformTypeIdentifiers struct Gif: StickerProtocol { var id: UUID - var uiID: UUID var name: String var localImageURLString: String { let urlString = remoteImageURL.absoluteString @@ -32,7 +31,6 @@ struct Gif: StickerProtocol { id: UUID = UUID() ) { self.id = id - self.uiID = id self.name = name self.remoteImageURL = url } @@ -56,11 +54,6 @@ struct Gif: StickerProtocol { return } - @MainActor - mutating func refresh() { - withAnimation { self.uiID = UUID() } - } - static var test: Gif = Gif( name: "doesheknow", url: URL(string: "https://media1.tenor.com/m/FN9udnZmQU8AAAAd/does-he-know-batman.gif")! diff --git a/StickerSlack/Stickers/Sticker.swift b/StickerSlack/Stickers/Sticker.swift index bead561..0974ba1 100644 --- a/StickerSlack/Stickers/Sticker.swift +++ b/StickerSlack/Stickers/Sticker.swift @@ -33,6 +33,25 @@ extension StickerProtocol { } } + nonisolated + func downloadImage() async throws { + if let data = try? await Data(contentsOf: localImageURL), + let _ = UIImage(data: data) { + return + } + + var (data, _) = try await URLSession.shared.data(from: remoteImageURL) + + if let uiImage = UIImage(data: data), + let cgImage = uiImage.cgImage, + await !self.localImageURLString.contains(".gif"), + cgImage.width < 300 || cgImage.height < 300 { + data = await resize(image: uiImage, to: CGSize(width: 300, height: 300)).pngData()! + } + try! await data.write(to: localImageURL) + return + } + func deleteImage() { try? FileManager.default.removeItem(at: localImageURL) return diff --git a/StickerSlack/Stickers/StickerProtocol.swift b/StickerSlack/Stickers/StickerProtocol.swift index e18c9c9..9c54041 100644 --- a/StickerSlack/Stickers/StickerProtocol.swift +++ b/StickerSlack/Stickers/StickerProtocol.swift @@ -10,7 +10,6 @@ import Messages protocol StickerProtocol: Codable, Identifiable, Hashable { var id: UUID { get set } - var uiID: UUID { get set } var name: String { get set } var localImageURL: URL { get } @@ -24,7 +23,6 @@ protocol StickerProtocol: Codable, Identifiable, Hashable { func downloadImage() async throws func deleteImage() - mutating func refresh() func resize(image: UIImage, to targetSize: CGSize) -> UIImage static var test: Self { get } } diff --git a/StickerSlack/SwiftUI/Emoji/EmojiRow.swift b/StickerSlack/SwiftUI/Emoji/EmojiRow.swift index 1203126..d895988 100644 --- a/StickerSlack/SwiftUI/Emoji/EmojiRow.swift +++ b/StickerSlack/SwiftUI/Emoji/EmojiRow.swift @@ -15,26 +15,38 @@ struct EmojiRow: View { var body: some View { HStack { EmojiPreview(hoarder: hoarder, emoji: emoji) - .frame(maxWidth: 100, maxHeight: 100) - .padding(.trailing, 20) + .frame(width: 100, height: 100) + .padding(.trailing, 10) - Text(emoji.name) + VStack(alignment: .leading, spacing: 5) { + ZStack { + RoundedRectangle(cornerRadius: 5) + .foregroundStyle(.gray.opacity(0.1)) + Text(emoji.name) + .font(.caption) + .bold(hoarder.downloadedEmojis.contains(emoji.name)) + .foregroundColor(hoarder.downloadedEmojis.contains(emoji.name) ? .green : .primary) + .padding(3) + } + .fixedSize() + if hoarder.downloadedEmojis.contains(emoji.name) { + Image(systemName: "arrow.down.circle.fill") + .resizable().scaledToFit() + .frame(width: 20, height: 20) + .symbolRenderingMode(.hierarchical) + .foregroundStyle(.gray) + .transition(.scale) + } + } Spacer() - Image(systemName: "arrow.down.circle.fill") - .resizable().scaledToFit() - .frame(width: 20, height: 20) - .symbolRenderingMode(.hierarchical) - .foregroundStyle(.gray) - .padding(.trailing, 20) - .opacity(hoarder.downloadedEmojis.contains(emoji.name) ? 1 : 0) - if hoarder.downloadedEmojis.contains(emoji.name) { Button("", systemImage: "trash") { hoarder.delete(emoji: emoji) } .buttonStyle(.plain) + .transition(.scale) } else { Button("", systemImage: "arrow.down.circle") { Task { @@ -42,14 +54,21 @@ struct EmojiRow: View { } } .buttonStyle(.plain) + .transition(.scale) } } } } #Preview { - EmojiRow( - hoarder: EmojiHoarder(localOnly: true), - emoji: Emoji.test - ) + List { + EmojiRow( + hoarder: EmojiHoarder(localOnly: true), + emoji: Emoji.test + ) + EmojiRow( + hoarder: EmojiHoarder(localOnly: true), + emoji: Emoji.test + ) + } } diff --git a/StickerSlack/SwiftUI/SearchView.swift b/StickerSlack/SwiftUI/SearchView.swift index 0b74478..1dc978a 100644 --- a/StickerSlack/SwiftUI/SearchView.swift +++ b/StickerSlack/SwiftUI/SearchView.swift @@ -18,7 +18,7 @@ struct SearchView: View { EmojiRow(hoarder: hoarder, emoji: hoarder.trie.dict[name]!) } .onChange(of: hoarder.searchTerm) { _ in - withAnimation { filterResult = hoarder.trie.search(prefix: hoarder.searchTerm) } + withAnimation(.snappy) { filterResult = hoarder.trie.search(prefix: hoarder.searchTerm) } } } .searchable(text: $hoarder.searchTerm) diff --git a/StickerSlack/Trie/TrieTestingView.swift b/StickerSlack/Trie/TrieTestingView.swift index cab7531..f35da83 100644 --- a/StickerSlack/Trie/TrieTestingView.swift +++ b/StickerSlack/Trie/TrieTestingView.swift @@ -55,7 +55,7 @@ struct TrieTestingView: View { .textFieldStyle(.roundedBorder) .border(.orange) .onChange(of: filterTerm) { _ in - withAnimation { filterResult = hoarder.trie.search(prefix: filterTerm) } + withAnimation(.snappy) { filterResult = hoarder.trie.search(prefix: filterTerm) } } Text("\(filterResult.count)") .modifier(numericTextCompat()) @@ -65,7 +65,7 @@ struct TrieTestingView: View { .textFieldStyle(.roundedBorder) .border(.orange) .onChange(of: filterTerm2) { _ in - withAnimation { filterResult = hoarder.emojis.filter({ $0.name.localizedCaseInsensitiveContains(filterTerm2) }).map({ $0.name }) } + withAnimation(.snappy) { filterResult = hoarder.emojis.filter({ $0.name.localizedCaseInsensitiveContains(filterTerm2) }).map({ $0.name }) } } if uikit {