create singleton for PBManager

This commit is contained in:
leminlimez committed 2025-06-08 14:32:58 -04:00
1 parent eb280c5ecb
commit 35e58b88eb
4 files changed
+33 -33

No files matched your search

@@ -19,7 +19,7 @@ class DownloadManager {
let (data, response) = try await URLSession.shared.data(for: request) as! (Data, HTTPURLResponse) let (data, response) = try await URLSession.shared.data(for: request) as! (Data, HTTPURLResponse)
guard response.statusCode == 200 else { throw URLError(.cannotConnectToHost) } guard response.statusCode == 200 else { throw URLError(.cannotConnectToHost) }
let newURL = PosterBoardManager.getTendiesStoreURL().appendingPathComponent(getWallpaperNameFromURL(string: url.absoluteString)) let newURL = PosterBoardManager.shared.getTendiesStoreURL().appendingPathComponent(getWallpaperNameFromURL(string: url.absoluteString))
try data.write(to: newURL) try data.write(to: newURL)
return newURL return newURL
} }
@@ -37,7 +37,7 @@ class DownloadManager {
url.stopAccessingSecurityScopedResource() url.stopAccessingSecurityScopedResource()
} }
} }
let newURL = PosterBoardManager.getTendiesStoreURL().appendingPathComponent(url.lastPathComponent) let newURL = PosterBoardManager.shared.getTendiesStoreURL().appendingPathComponent(url.lastPathComponent)
try FileManager.default.copyItem(at: url, to: newURL) try FileManager.default.copyItem(at: url, to: newURL)
return newURL return newURL
} }
@@ -9,13 +9,17 @@ import Foundation
import ZIPFoundation import ZIPFoundation
import UIKit import UIKit
class PosterBoardManager { class PosterBoardManager: ObservableObject {
static let ShortcutURL = "https://www.icloud.com/shortcuts/a28d2c02ca11453cb5b8f91c12cfa692" static let ShortcutURL = "https://www.icloud.com/shortcuts/a28d2c02ca11453cb5b8f91c12cfa692"
static let WallpapersURL = "https://cowabun.ga/wallpapers" static let WallpapersURL = "https://cowabun.ga/wallpapers"
static let MaxTendies = 10 static let MaxTendies = 10
static func getTendiesStoreURL() -> URL { static let shared = PosterBoardManager()
@Published var selectedTendies: [URL] = []
func getTendiesStoreURL() -> URL {
let tendiesStoreURL = SymHandler.getDocumentsDirectory().appendingPathComponent("KFC Bucket", conformingTo: .directory) let tendiesStoreURL = SymHandler.getDocumentsDirectory().appendingPathComponent("KFC Bucket", conformingTo: .directory)
// create it if it doesn't exist // create it if it doesn't exist
if !FileManager.default.fileExists(atPath: tendiesStoreURL.path()) { if !FileManager.default.fileExists(atPath: tendiesStoreURL.path()) {
@@ -24,7 +28,7 @@ class PosterBoardManager {
return tendiesStoreURL return tendiesStoreURL
} }
static func openPosterBoard() -> Bool { func openPosterBoard() -> Bool {
guard let obj = objc_getClass("LSApplicationWorkspace") as? NSObject else { return false } guard let obj = objc_getClass("LSApplicationWorkspace") as? NSObject else { return false }
let workspace = obj.perform(Selector(("defaultWorkspace")))?.takeUnretainedValue() as? NSObject let workspace = obj.perform(Selector(("defaultWorkspace")))?.takeUnretainedValue() as? NSObject
@@ -35,7 +39,7 @@ class PosterBoardManager {
return false return false
} }
private static func unzipFile(at url: URL) throws -> URL { private func unzipFile(at url: URL) throws -> URL {
let fileName = url.deletingPathExtension().lastPathComponent let fileName = url.deletingPathExtension().lastPathComponent
let fileData = try Data(contentsOf: url) let fileData = try Data(contentsOf: url)
let fileManager = FileManager() let fileManager = FileManager()
@@ -68,13 +72,13 @@ class PosterBoardManager {
return destinationURL return destinationURL
} }
static func runShortcut(named name: String) { func runShortcut(named name: String) {
guard let urlEncodedName = name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), guard let urlEncodedName = name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: "shortcuts://run-shortcut?name=\(name)") else { return } let url = URL(string: "shortcuts://run-shortcut?name=\(name)") else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil) UIApplication.shared.open(url, options: [:], completionHandler: nil)
} }
static func getDescriptorsFromTendie(_ url: URL) throws -> [String: [URL]]? { func getDescriptorsFromTendie(_ url: URL) throws -> [String: [URL]]? {
for dir in try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) { for dir in try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) {
let fileName = dir.lastPathComponent let fileName = dir.lastPathComponent
if fileName.lowercased() == "container" { if fileName.lowercased() == "container" {
@@ -99,7 +103,7 @@ class PosterBoardManager {
return nil return nil
} }
static func randomizeWallpaperId(url: URL) throws { func randomizeWallpaperId(url: URL) throws {
let randomizedID = Int.random(in: 9999...99999) let randomizedID = Int.random(in: 9999...99999)
var files = [URL]() var files = [URL]()
if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) { if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
@@ -157,10 +161,10 @@ class PosterBoardManager {
} }
} }
static func applyTendies(_ urls: [URL], appHash: String) throws { func applyTendies(appHash: String) throws {
// organize the descriptors into their respective extensions // organize the descriptors into their respective extensions
var extList: [String: [URL]] = [:] var extList: [String: [URL]] = [:]
for url in urls { for url in selectedTendies {
let unzippedDir = try unzipFile(at: url) let unzippedDir = try unzipFile(at: url)
guard let descriptors = try getDescriptorsFromTendie(unzippedDir) else { continue } // TODO: Add error handling guard let descriptors = try getDescriptorsFromTendie(unzippedDir) else { continue } // TODO: Add error handling
extList.merge(descriptors) { (first, second) in first + second } extList.merge(descriptors) { (first, second) in first + second }
@@ -188,7 +192,7 @@ class PosterBoardManager {
} }
// clean up all possible files // clean up all possible files
for url in urls { for url in selectedTendies {
try? FileManager.default.removeItem(at: SymHandler.getDocumentsDirectory().appendingPathComponent("UnzipItems", conformingTo: .directory)) try? FileManager.default.removeItem(at: SymHandler.getDocumentsDirectory().appendingPathComponent("UnzipItems", conformingTo: .directory))
try? FileManager.default.removeItem(at: SymHandler.getDocumentsDirectory().appendingPathComponent(url.lastPathComponent)) try? FileManager.default.removeItem(at: SymHandler.getDocumentsDirectory().appendingPathComponent(url.lastPathComponent))
try? FileManager.default.removeItem(at: SymHandler.getDocumentsDirectory().appendingPathComponent(url.deletingPathExtension().lastPathComponent)) try? FileManager.default.removeItem(at: SymHandler.getDocumentsDirectory().appendingPathComponent(url.deletingPathExtension().lastPathComponent))
+5 -7
View File
@@ -13,8 +13,6 @@ struct Pocket_PosterApp: App {
@AppStorage("finishedTutorial") var finishedTutorial: Bool = false @AppStorage("finishedTutorial") var finishedTutorial: Bool = false
@AppStorage("pbHash") var pbHash: String = "" @AppStorage("pbHash") var pbHash: String = ""
@State var selectedTendies: [URL] = []
@State var downloadURL: String? = nil @State var downloadURL: String? = nil
@State var showDownloadAlert = false @State var showDownloadAlert = false
@@ -22,7 +20,7 @@ struct Pocket_PosterApp: App {
WindowGroup { WindowGroup {
Group { Group {
if finishedTutorial { if finishedTutorial {
ContentView(selectedTendies: $selectedTendies) ContentView()
} else { } else {
OnBoardingView(cards: onBoardingCards, isFinished: $finishedTutorial) OnBoardingView(cards: onBoardingCards, isFinished: $finishedTutorial)
} }
@@ -43,7 +41,7 @@ struct Pocket_PosterApp: App {
// prohibit to only tendies files // prohibit to only tendies files
if !url.absoluteString.hasSuffix(".tendies") { if !url.absoluteString.hasSuffix(".tendies") {
UIApplication.shared.alert(body: "Only .tendies files can be downloaded!") UIApplication.shared.alert(body: "Only .tendies files can be downloaded!")
} else if selectedTendies.count >= PosterBoardManager.MaxTendies { } else if PosterBoardManager.shared.selectedTendies.count >= PosterBoardManager.MaxTendies {
UIApplication.shared.alert(title: "Max Tendies Reached", body: "You can only apply \(PosterBoardManager.MaxTendies) descriptors.") UIApplication.shared.alert(title: "Max Tendies Reached", body: "You can only apply \(PosterBoardManager.MaxTendies) descriptors.")
} else { } else {
downloadURL = url.absoluteString.replacingOccurrences(of: "pocketposter://download?url=", with: "") downloadURL = url.absoluteString.replacingOccurrences(of: "pocketposter://download?url=", with: "")
@@ -55,13 +53,13 @@ struct Pocket_PosterApp: App {
pbHash = url.absoluteString.replacingOccurrences(of: "pocketposter://app-hash?uuid=", with: "") pbHash = url.absoluteString.replacingOccurrences(of: "pocketposter://app-hash?uuid=", with: "")
} }
else if url.pathExtension == "tendies" { else if url.pathExtension == "tendies" {
if selectedTendies.count >= PosterBoardManager.MaxTendies { if PosterBoardManager.shared.selectedTendies.count >= PosterBoardManager.MaxTendies {
UIApplication.shared.alert(title: "Max Tendies Reached", body: "You can only apply \(PosterBoardManager.MaxTendies) descriptors.") UIApplication.shared.alert(title: "Max Tendies Reached", body: "You can only apply \(PosterBoardManager.MaxTendies) descriptors.")
} else { } else {
// copy it over to the KFC bucket // copy it over to the KFC bucket
do { do {
let newURL = try DownloadManager.copyTendies(from: url) let newURL = try DownloadManager.copyTendies(from: url)
selectedTendies.append(newURL) PosterBoardManager.shared.selectedTendies.append(newURL)
Haptic.shared.notify(.success) Haptic.shared.notify(.success)
UIApplication.shared.alert(title: "Successfully imported \(url.lastPathComponent)", body: "") UIApplication.shared.alert(title: "Successfully imported \(url.lastPathComponent)", body: "")
} catch { } catch {
@@ -81,7 +79,7 @@ struct Pocket_PosterApp: App {
Task { Task {
do { do {
let newURL = try await DownloadManager.downloadFromURL(string: downloadURL!) let newURL = try await DownloadManager.downloadFromURL(string: downloadURL!)
selectedTendies.append(newURL) PosterBoardManager.shared.selectedTendies.append(newURL)
Haptic.shared.notify(.success) Haptic.shared.notify(.success)
UIApplication.shared.dismissAlert(animated: true) UIApplication.shared.dismissAlert(animated: true)
} catch { } catch {
+12 -14
View File
@@ -21,7 +21,6 @@ struct ContentView: View {
private let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "" private let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
@State var showTendiesImporter: Bool = false @State var showTendiesImporter: Bool = false
var selectedTendies: Binding<[URL]>
@State var showErrorAlert = false @State var showErrorAlert = false
@State var lastError: String? @State var lastError: String?
@@ -47,9 +46,9 @@ struct ContentView: View {
.listRowInsets(EdgeInsets()) .listRowInsets(EdgeInsets())
.padding(7) .padding(7)
if !selectedTendies.wrappedValue.isEmpty { if !PosterBoardManager.shared.selectedTendies.isEmpty {
Section { Section {
ForEach(selectedTendies.wrappedValue, id: \.self) { tendie in ForEach(PosterBoardManager.shared.selectedTendies, id: \.self) { tendie in
Text(tendie.deletingPathExtension().lastPathComponent) Text(tendie.deletingPathExtension().lastPathComponent)
} }
.onDelete(perform: delete) .onDelete(perform: delete)
@@ -63,23 +62,23 @@ struct ContentView: View {
Text("Enter your PosterBoard app hash in Settings.") Text("Enter your PosterBoard app hash in Settings.")
} else { } else {
VStack { VStack {
if !selectedTendies.wrappedValue.isEmpty { if !PosterBoardManager.shared.selectedTendies.isEmpty {
Button(action: { Button(action: {
UIImpactFeedbackGenerator(style: .soft).impactOccurred() UIImpactFeedbackGenerator(style: .soft).impactOccurred()
UIApplication.shared.alert(title: NSLocalizedString("Applying Tendies...", comment: ""), body: NSLocalizedString("Please wait", comment: ""), animated: false, withButton: false) UIApplication.shared.alert(title: NSLocalizedString("Applying Tendies...", comment: ""), body: NSLocalizedString("Please wait", comment: ""), animated: false, withButton: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
do { do {
try PosterBoardManager.applyTendies(selectedTendies.wrappedValue, appHash: pbHash) try PosterBoardManager.shared.applyTendies(appHash: pbHash)
selectedTendies.wrappedValue.removeAll() PosterBoardManager.shared.selectedTendies.removeAll()
SymHandler.cleanup() // just to be extra sure SymHandler.cleanup() // just to be extra sure
try? FileManager.default.removeItem(at: PosterBoardManager.getTendiesStoreURL()) try? FileManager.default.removeItem(at: PosterBoardManager.shared.getTendiesStoreURL())
Haptic.shared.notify(.success) Haptic.shared.notify(.success)
UIApplication.shared.dismissAlert(animated: false) UIApplication.shared.dismissAlert(animated: false)
UIApplication.shared.confirmAlert(title: "Success!", body: "The PosterBoard app will now open. Please close it from the app switcher.", onOK: { UIApplication.shared.confirmAlert(title: "Success!", body: "The PosterBoard app will now open. Please close it from the app switcher.", onOK: {
if !PosterBoardManager.openPosterBoard() { if !PosterBoardManager.shared.openPosterBoard() {
UIApplication.shared.confirmAlert(title: "Falling Back to Shortcut", body: "PosterBoard failed to open directly. The fallback shortcut will now be opened.", onOK: { UIApplication.shared.confirmAlert(title: "Falling Back to Shortcut", body: "PosterBoard failed to open directly. The fallback shortcut will now be opened.", onOK: {
PosterBoardManager.runShortcut(named: "PosterBoard") PosterBoardManager.shared.runShortcut(named: "PosterBoard")
}, noCancel: true) }, noCancel: true)
} }
}, noCancel: true) }, noCancel: true)
@@ -130,10 +129,10 @@ struct ContentView: View {
.fileImporter(isPresented: $showTendiesImporter, allowedContentTypes: [UTType(filenameExtension: "tendies", conformingTo: .data)!], allowsMultipleSelection: true, onCompletion: { result in .fileImporter(isPresented: $showTendiesImporter, allowedContentTypes: [UTType(filenameExtension: "tendies", conformingTo: .data)!], allowsMultipleSelection: true, onCompletion: { result in
switch result { switch result {
case .success(let url): case .success(let url):
if selectedTendies.wrappedValue.count + url.count > PosterBoardManager.MaxTendies { if PosterBoardManager.shared.selectedTendies.count + url.count > PosterBoardManager.MaxTendies {
UIApplication.shared.alert(title: "Max Tendies Reached", body: "You can only apply \(PosterBoardManager.MaxTendies) descriptors.") UIApplication.shared.alert(title: "Max Tendies Reached", body: "You can only apply \(PosterBoardManager.MaxTendies) descriptors.")
} else { } else {
selectedTendies.wrappedValue.append(contentsOf: url) PosterBoardManager.shared.selectedTendies.append(contentsOf: url)
} }
case .failure(let error): case .failure(let error):
lastError = error.localizedDescription lastError = error.localizedDescription
@@ -154,11 +153,10 @@ struct ContentView: View {
} }
func delete(at offsets: IndexSet) { func delete(at offsets: IndexSet) {
selectedTendies.wrappedValue.remove(atOffsets: offsets) PosterBoardManager.shared.selectedTendies.remove(atOffsets: offsets)
} }
init(selectedTendies: Binding<[URL]>) { init() {
self.selectedTendies = selectedTendies
// Fix file picker // Fix file picker
let fixMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.fix_init(forOpeningContentTypes:asCopy:)))! let fixMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.fix_init(forOpeningContentTypes:asCopy:)))!
let origMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.init(forOpeningContentTypes:asCopy:)))! let origMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.init(forOpeningContentTypes:asCopy:)))!