updated downloadImage()

- to use cached local version if available
 - now writes to disk if not already saved

rewrote toEmojis() just a simple map of ApiEmoji($0).toEmoji()
use the new toEmojis in emojiHoarder

now the emojiDB gets stored locally, need to make it fetch it if it can cos cache cant be valid forever yk

using neon443/Haptics for haptics lol
added a download button
on EmojiPreview, if downloaded a lil download icon shows up, and will show that one instead of AsyncImage
This commit is contained in:
neon443
2025-10-23 09:46:53 +01:00
parent 2841bc2109
commit 4ed5695f4a
6 changed files with 93 additions and 43 deletions

View File

@@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
A924C3722EA9127200F20781 /* 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 */; };
A940FE3D2EA232590016870B /* ApiEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = A940FE3C2EA232590016870B /* ApiEmoji.swift */; };
A949B1F32EA04E8200215164 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A949B1EF2EA04E8200215164 /* Assets.xcassets */; };
A949B1F42EA04E8200215164 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A949B1F02EA04E8200215164 /* ContentView.swift */; };
@@ -77,6 +78,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A924C3782EA9225800F20781 /* Haptics in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -190,6 +192,7 @@
);
name = StickerSlack;
packageProductDependencies = (
A924C3772EA9225800F20781 /* Haptics */,
);
productName = StickerSlack;
productReference = A949B1DF2EA04C0B00215164 /* StickerSlack.app */;
@@ -241,6 +244,9 @@
);
mainGroup = A949B1D62EA04C0B00215164;
minimizedProjectReferenceProxies = 1;
packageReferences = (
A924C3762EA9225800F20781 /* XCLocalSwiftPackageReference "../Haptics" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = A949B1E02EA04C0B00215164 /* Products */;
projectDirPath = "";
@@ -624,6 +630,20 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
A924C3762EA9225800F20781 /* XCLocalSwiftPackageReference "../Haptics" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../Haptics;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
A924C3772EA9225800F20781 /* Haptics */ = {
isa = XCSwiftPackageProductDependency;
productName = Haptics;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = A949B1D72EA04C0B00215164 /* Project object */;
}

View File

@@ -48,7 +48,6 @@ struct Emoji: Codable, Identifiable, Hashable {
self.remoteImageURL = apiEmoji.url
self.localImageURL = EmojiHoarder.container.appendingPathComponent(id.uuidString, conformingTo: .image)
// Task { [weak self] in
// let (data, response) = try await URLSession.shared.data(from: apiEmoji.url)
// self.image = UIImage(data: data)
@@ -58,8 +57,13 @@ struct Emoji: Codable, Identifiable, Hashable {
// self.image = UIImage(data: image) ?? UIImage()
}
func loadImage() async throws -> UIImage {
func downloadImage() async throws -> UIImage {
if let data = try? Data(contentsOf: localImageURL),
let uiimage = UIImage(data: data) {
return uiimage
}
let (data, _) = try await URLSession.shared.data(from: remoteImageURL)
try! data.write(to: localImageURL)
return UIImage(data: data)!
}
}

View File

@@ -12,20 +12,8 @@ struct SlackResponse: Codable {
var imageUrl: String
var alias: String?
// func toEmojis() -> [Emoji] {
// let initialMap = emoji.map {
// Emoji(name: $0.key, url: $0.value)
// }
// return initialMap.map {
// var ret = $0
// if ret.urlString.prefix(6) == "alias:" {
// if let orig = initialMap.first(where: {
// $0.name == "\(ret.urlString.dropFirst(6))"
// }) {
// ret.urlString = orig.urlString
// }
// }
// return ret
// }
// }
static func toEmojis(from: [SlackResponse]) -> [Emoji] {
return from.map { ApiEmoji(name: $0.name, url: $0.imageUrl).toEmoji() }
}
}

View File

@@ -8,22 +8,28 @@
import Foundation
import SwiftUI
import Combine
import UniformTypeIdentifiers
class EmojiHoarder: ObservableObject {
static let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.neon443.StickerSlack")!
static let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.neon443.StickerSlack")!.appendingPathComponent("Library", conformingTo: .directory)
static let localEmojiDB: URL = EmojiHoarder.container.appendingPathExtension("localEmojiDB.json")
private let endpoint: URL = URL(string: "https://cachet.dunkirk.sh/emojis")!
@Published var emojis: [Emoji]
init() {
if let localEmojiDB = try? Data(contentsOf: EmojiHoarder.localEmojiDB) {
let decoded = try! JSONDecoder().decode([Emoji].self, from: localEmojiDB)
self.emojis = decoded
return
}
guard let data = try? Data(contentsOf: endpoint) else { fatalError("cachet unreachable") }
let decoded: [SlackResponse] = try! JSONDecoder().decode([SlackResponse].self, from: data)
self.emojis = decoded.map { ApiEmoji(name: $0.name, url: $0.imageUrl).toEmoji() }
// Task {
// for i in emojis.indices {
// emojis[i].image = try? await emojis[i].loadImage()
// }
// }
self.emojis = SlackResponse.toEmojis(from: decoded)
try! JSONEncoder().encode(emojis).write(to: EmojiHoarder.localEmojiDB)
}
// func storeStickers

View File

@@ -6,6 +6,7 @@
//
import SwiftUI
import Haptics
struct ContentView: View {
@StateObject var hoarder: EmojiHoarder = EmojiHoarder()
@@ -15,8 +16,18 @@ struct ContentView: View {
TabView {
List {
ForEach(hoarder.emojis, id: \.self) { emoji in
HStack {
EmojiPreview(emoji: emoji)
.frame(maxWidth: 100)
Spacer()
Button("", systemImage: "arrow.down.circle") {
Task {
let _ = try? await emoji.downloadImage()
Haptic.success.trigger()
}
}
.buttonStyle(.plain)
}
}
}
.tabItem {

View File

@@ -6,20 +6,32 @@
//
import SwiftUI
import Haptics
struct EmojiPreview: View {
@State var emoji: Emoji
@State private var id: UUID = UUID()
@State private var image: UIImage?
var body: some View {
VStack {
Text(emoji.name)
if let image = emoji.image {
Group {
if let localImage = try? Data(contentsOf: emoji.localImageURL),
let image = UIImage(data: localImage) {
Image(uiImage: image)
} else {
ProgressView()
.resizable().scaledToFit()
.border(.orange)
.overlay(alignment: .bottomLeading) {
Image(systemName: "arrow.down.circle.fill")
.resizable().scaledToFit()
.frame(width: 20, height: 20)
.symbolRenderingMode(.hierarchical)
// .foregroundStyle(.gray)
}
} else {
AsyncImage(url: emoji.remoteImageURL) { phase in
if let image = phase.image {
image
@@ -27,6 +39,7 @@ struct EmojiPreview: View {
} else if phase.error != nil {
Image(systemName: "xmark.app.fill")
.resizable().scaledToFit()
.padding()
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.red)
.onTapGesture {
@@ -36,6 +49,14 @@ struct EmojiPreview: View {
ProgressView()
}
}
}
}
.onTapGesture {
Task {
image = try? await emoji.downloadImage()
Haptic.success.trigger()
}
}
.id(id)
}
}