mirror of
https://github.com/neon443/StickerSlack.git
synced 2026-03-11 05:19:13 +00:00
animations in emojirow
animated deleting/downloading emojis stopped it glitching when downloading/deleting an emoji removed refresh() and uiID cos its not necessary sob moved downloadImage() to stickerprotocol extension bundled an image so test emoji loading is faster
This commit is contained in:
BIN
Resources/image.png
Normal file
BIN
Resources/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 MiB |
@@ -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 = "<group>"; };
|
||||
A986A6CA2EB659E000B6E0FA /* MessagesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = "<group>"; };
|
||||
A986A6CB2EB659E000B6E0FA /* StickerBrowserDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerBrowserDataSource.swift; sourceTree = "<group>"; };
|
||||
A99589AE2F5E0D83003A4A8C /* image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = image.png; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
A9BBC5172EB8FA4500FFE82F /* ViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = "<group>"; };
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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")!
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
withAnimation(.snappy) {
|
||||
downloadedEmojis.remove(emoji.name)
|
||||
downloadedEmojisArr.removeAll(where: { $0 == emoji.name })
|
||||
}
|
||||
storeDownloadedIndexes()
|
||||
}
|
||||
self.trie.dict[emoji.name]?.refresh()
|
||||
if !skipStoreIndex { Haptic.heavy.trigger() }
|
||||
}
|
||||
|
||||
|
||||
@@ -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")!
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.foregroundStyle(.gray.opacity(0.1))
|
||||
Text(emoji.name)
|
||||
|
||||
Spacer()
|
||||
|
||||
.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)
|
||||
.padding(.trailing, 20)
|
||||
.opacity(hoarder.downloadedEmojis.contains(emoji.name) ? 1 : 0)
|
||||
.transition(.scale)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
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 {
|
||||
List {
|
||||
EmojiRow(
|
||||
hoarder: EmojiHoarder(localOnly: true),
|
||||
emoji: Emoji.test
|
||||
)
|
||||
EmojiRow(
|
||||
hoarder: EmojiHoarder(localOnly: true),
|
||||
emoji: Emoji.test
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user