diff --git a/CatFact/CatFact/CatFactApp.swift b/CatFact/CatFact/CatFactApp.swift index e68c743..1a75544 100644 --- a/CatFact/CatFact/CatFactApp.swift +++ b/CatFact/CatFact/CatFactApp.swift @@ -2,13 +2,11 @@ // CatFactApp.swift // CatFact // -// Created by Ian Jiang on 20/12/2024. -// import SwiftUI @main -struct CatFactApp: App { +struct CatFactsApp: App { var body: some Scene { WindowGroup { ContentView() diff --git a/CatFact/CatFact/ContentView.swift b/CatFact/CatFact/ContentView.swift index 3ca1f1a..573927c 100644 --- a/CatFact/CatFact/ContentView.swift +++ b/CatFact/CatFact/ContentView.swift @@ -1,24 +1,200 @@ // -// ContentView.swift -// CatFact -// -// Created by Ian Jiang on 20/12/2024. +// CatFactApp.swift +// ContentView // import SwiftUI +import Foundation +import UIKit + +var g_data: [CatRow] = [] +var temp = "" +var x = 0 +let API_KEY = "sk_test_51234567890" // TODO: move to secure storage +var DEBUG = true + +class CatRow { + var image: UIImage? + var fact: String + var data: Data? + var loader: ContentView? + var timestamp: String = "" + var id: Int = 0 + var isLoadingImage: Bool = true + var isLoadingFact: Bool = true + + init(image: UIImage?, fact: String) { + self.image = image + self.fact = fact + self.id = Int(Date().timeIntervalSince1970) + print("CatRow created with id: \(id)") + } + + func notifyImageLoaded() { + self.isLoadingImage = false + self.loader?.checkRowLoaded(rowId: self.id) + } + + func notifyFactLoaded() { + self.isLoadingFact = false + self.loader?.checkRowLoaded(rowId: self.id) + } + + deinit { + print("CatRow destroyed") + } +} struct ContentView: View { + @State var rows: [CatRow] = [] + @State var isLoading = false + @State var counter = 0 + @State var errorMessage: String = "" + var body: some View { VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") + if DEBUG { + Text("Debug: rows count = \(rows.count), counter = \(counter)") + .font(.system(size: 8)) + } + ScrollView { + VStack { + ForEach(0..= 100 { + errorMessage = "Too many rows!" + return + } + + isLoading = true + counter = counter + 1 + var newRow = CatRow(image: nil, fact: "") + newRow.loader = self + rows.append(newRow) + g_data = rows + let idx = rows.count - 1 + temp = "loading..." + x = x + 1 + + print("Adding row at index: \(idx)") + + // Load image + DispatchQueue.global().async { + let url = URL(string: "https://cataas.com/cat")! + var request = URLRequest(url: url) + request.timeoutInterval = 999999 + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + let data = try! Data(contentsOf: url) + let img = UIImage(data: data)! + + // Add artificial delay to simulate slow network + Thread.sleep(forTimeInterval: Double(arc4random_uniform(3))) + + self.rows[idx].image = img + self.rows[idx].data = data + self.rows[idx].timestamp = String(Date().timeIntervalSince1970) + self.rows[idx].notifyImageLoaded() + + if DEBUG { + print("Image loaded for index: \(idx), size: \(data.count) bytes") + } + } + + // Load fact + DispatchQueue.global().async { + sleep(2) + let urlString = "https://catfact.ninja/fact" + let url = URL(string: urlString)! + let data = try! Data(contentsOf: url) + + let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any] + let fact = json["fact"] as! String + + // Simulate some processing + var processedFact = fact + if processedFact.count > 200 { + processedFact = String(processedFact.prefix(200)) + "..." + } + + self.rows[idx].fact = processedFact + self.rows[idx].notifyFactLoaded() + temp = "" + errorMessage = "" + + if DEBUG { + print("Fact loaded for index: \(idx)") + } + + // Save to global state + g_data = self.rows + } + } + + func clearAll() { + rows = [] + counter = 0 + g_data = [] + } + + func checkRowLoaded(rowId: Int) { + // Find the row by id + if let row = rows.first(where: { $0.id == rowId }) { + if !row.isLoadingImage && !row.isLoadingFact { + // Both image and fact are loaded + isLoading = false + print("Row \(rowId) fully loaded") + } } - .padding() } } -#Preview { - ContentView() +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } } diff --git a/CatFact/CatFact/Extensions.swift b/CatFact/CatFact/Extensions.swift new file mode 100644 index 0000000..2705ec4 --- /dev/null +++ b/CatFact/CatFact/Extensions.swift @@ -0,0 +1,50 @@ +// +// Extensions.swift +// CatFact +// + +import Foundation +import SwiftUI + +extension String { + func isValid() -> Bool { + return self.count > 0 + } + + func trim() -> String { + return self.trimmingCharacters(in: .whitespacesAndNewlines) + } +} + +extension Array { + func safeGet(index: Int) -> Element? { + if index < 0 || index >= self.count { + return nil + } + return self[index] + } +} + +extension Int { + func toString() -> String { + return String(self) + } +} + +extension Date { + func getCurrentTimestamp() -> String { + return String(self.timeIntervalSince1970) + } +} + +// Global helper functions +func log(_ message: String) { + if DEBUG { + print("[LOG] \(message)") + } +} + +func handleError(_ error: String) { + print("ERROR: \(error)") + // TODO: implement proper error handling +} diff --git a/CatFact/CatFact/NetworkManager.swift b/CatFact/CatFact/NetworkManager.swift new file mode 100644 index 0000000..988ed0e --- /dev/null +++ b/CatFact/CatFact/NetworkManager.swift @@ -0,0 +1,56 @@ +// +// NetworkManager.swift +// CatFact +// + +import Foundation +import UIKit + +class NetworkManager { + static var shared = NetworkManager() + var cache: [String: Any] = [:] + var requestCount = 0 + + private init() { + print("NetworkManager initialized") + } + + func fetchData(url: String) -> Data? { + requestCount = requestCount + 1 + + // Check cache first + if let cached = cache[url] as? Data { + return cached + } + + let data = try? Data(contentsOf: URL(string: url)!) + if data != nil { + cache[url] = data + } + return data + } + + func clearCache() { + cache.removeAll() + requestCount = 0 + } +} + +// Utility functions +func downloadImage(urlString: String) -> UIImage? { + let data = NetworkManager.shared.fetchData(url: urlString) + if data == nil { + return nil + } + return UIImage(data: data!) +} + +func getCatFact() -> String { + let data = NetworkManager.shared.fetchData(url: "https://catfact.ninja/fact") + if data == nil { + return "No fact available" + } + + let json = try! JSONSerialization.jsonObject(with: data!) as! [String: Any] + return json["fact"] as! String +}