diff --git a/Pocket Poster.xcodeproj/project.pbxproj b/Pocket Poster.xcodeproj/project.pbxproj index 285931e..6ebde64 100644 --- a/Pocket Poster.xcodeproj/project.pbxproj +++ b/Pocket Poster.xcodeproj/project.pbxproj @@ -14,9 +14,22 @@ 6F09B79D2DEB694B00CDE89C /* Pocket Poster.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Pocket Poster.app"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 6F09B7F72DEBBFEB00CDE89C /* Exceptions for "Pocket Poster" folder in "Pocket Poster" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + "Pocket-Poster-Info.plist", + ); + target = 6F09B79C2DEB694B00CDE89C /* Pocket Poster */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + /* Begin PBXFileSystemSynchronizedRootGroup section */ 6F09B79F2DEB694B00CDE89C /* Pocket Poster */ = { isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 6F09B7F72DEBBFEB00CDE89C /* Exceptions for "Pocket Poster" folder in "Pocket Poster" target */, + ); path = "Pocket Poster"; sourceTree = ""; }; @@ -267,6 +280,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Pocket-Poster-Info.plist"; + INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -298,6 +312,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Pocket-Poster-Info.plist"; + INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/Pocket Poster/Controllers/PosterBoardManager.swift b/Pocket Poster/Controllers/PosterBoardManager.swift index 1fd48ad..2b4d449 100644 --- a/Pocket Poster/Controllers/PosterBoardManager.swift +++ b/Pocket Poster/Controllers/PosterBoardManager.swift @@ -110,33 +110,35 @@ class PosterBoardManager { } } - static func applyTendie(_ url: URL, appHash: String) throws { - // scope the resource - let accessing = url.startAccessingSecurityScopedResource() - defer { - if accessing { - url.stopAccessingSecurityScopedResource() - } - } - - guard let descriptors = try getDescriptorsFromTendie(url) else { return } // TODO: Add error handling - // create the folder - for descr in try FileManager.default.contentsOfDirectory(at: descriptors, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) { - if descr.lastPathComponent != "__MACOSX" { - try randomizeWallpaperId(url: descr) - let newURL = SymHandler.getDocumentsDirectory().appendingPathComponent(UUID().uuidString, conformingTo: .directory) - try FileManager.default.moveItem(at: descr, to: newURL) - - let _ = try SymHandler.createDescriptorsSymlink(appHash: appHash) - do { - try FileManager.default.trashItem(at: newURL, resultingItemURL: nil) - } catch { - print(error.localizedDescription) + static func applyTendies(_ urls: [URL], appHash: String) throws { + let _ = try SymHandler.createDescriptorsSymlink(appHash: appHash) + for url in urls { + // scope the resource + let accessing = url.startAccessingSecurityScopedResource() + defer { + if accessing { + url.stopAccessingSecurityScopedResource() } - SymHandler.cleanup() } + + guard let descriptors = try getDescriptorsFromTendie(url) else { return } // TODO: Add error handling + // create the folder + for descr in try FileManager.default.contentsOfDirectory(at: descriptors, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) { + if descr.lastPathComponent != "__MACOSX" { + try randomizeWallpaperId(url: descr) + let newURL = SymHandler.getDocumentsDirectory().appendingPathComponent(UUID().uuidString, conformingTo: .directory) + try FileManager.default.moveItem(at: descr, to: newURL) + + do { + try FileManager.default.trashItem(at: newURL, resultingItemURL: nil) + } catch { + print(error.localizedDescription) + } + } + } + + try? FileManager.default.removeItem(at: descriptors) } - - try? FileManager.default.removeItem(at: descriptors) + SymHandler.cleanup() } } diff --git a/Pocket Poster/Extensions/MaterialView.swift b/Pocket Poster/Extensions/MaterialView.swift new file mode 100644 index 0000000..d64cdd6 --- /dev/null +++ b/Pocket Poster/Extensions/MaterialView.swift @@ -0,0 +1,24 @@ +// +// MaterialView.swift +// Pocket Poster +// +// Created by lemin on 5/31/25. +// + +import SwiftUI + +struct MaterialView: UIViewRepresentable { + let material: UIBlurEffect.Style + + init(_ material: UIBlurEffect.Style) { + self.material = material + } + + func makeUIView(context: Context) -> UIVisualEffectView { + UIVisualEffectView(effect: UIBlurEffect(style: material)) + } + + func updateUIView(_ uiView: UIVisualEffectView, context: Context) { + uiView.effect = UIBlurEffect(style: material) + } +} diff --git a/Pocket Poster/Extensions/TintedButton.swift b/Pocket Poster/Extensions/TintedButton.swift new file mode 100644 index 0000000..12660f6 --- /dev/null +++ b/Pocket Poster/Extensions/TintedButton.swift @@ -0,0 +1,56 @@ +// +// TintedButton.swift +// Pocket Poster +// +// Created by lemin on 5/31/25. +// + +import SwiftUI + +struct TintedButton: ButtonStyle { + var color: Color + var material: UIBlurEffect.Style? + var fullwidth: Bool = false + + func makeBody(configuration: Configuration) -> some View { + ZStack { + if fullwidth { + configuration.label + .padding(15) + .frame(maxWidth: .infinity) + .background(material == nil ? AnyView(color.opacity(0.2)) : AnyView(MaterialView(material!))) + .cornerRadius(8) + .foregroundColor(color) + } else { + configuration.label + .padding(15) + .background(material == nil ? AnyView(color.opacity(0.2)) : AnyView(MaterialView(material!))) + .cornerRadius(8) + .foregroundColor(color) + } + } + } + + init(color: Color = .blue, fullwidth: Bool = false) { + self.color = color + self.fullwidth = fullwidth + } + init(color: Color = .blue, material: UIBlurEffect.Style, fullwidth: Bool = false) { + self.color = color + self.material = material + self.fullwidth = fullwidth + } +} + + +#if DEBUG +struct FullwidthTintedButton_Previews: PreviewProvider { + static var previews: some View { + Button("Exaple") { + + } + .buttonStyle(TintedButton(color: .red, fullwidth: true)) + .padding() + } +} +#endif diff --git a/Pocket Poster/Views/ContentView.swift b/Pocket Poster/Views/ContentView.swift index 55378d9..1de2d64 100644 --- a/Pocket Poster/Views/ContentView.swift +++ b/Pocket Poster/Views/ContentView.swift @@ -13,54 +13,88 @@ struct ContentView: View { @AppStorage("pbHash") var pbHash: String = "" @State var showTendiesImporter: Bool = false - @State var selectedTendie: URL? = nil + @State var selectedTendies: [URL]? = nil @State var showErrorAlert = false + @State var showSuccessAlert = false @State var lastError: String? var body: some View { - VStack { - TextField("App Hash", text: $pbHash) - - Button(action: { - showTendiesImporter.toggle() - }) { - Text("Select Tendie") - } - - if let selectedTendie = selectedTendie { - Text("Selected Tendie: \(selectedTendie)") - if pbHash != "" { + NavigationStack { + ScrollView { + Section { + HStack { + Text("App Hash:") + .bold() + Spacer() + } + TextField("App Hash", text: $pbHash) + } + .padding(.bottom, 5) + + Section { Button(action: { - do { - try PosterBoardManager.applyTendie(selectedTendie, appHash: pbHash) - lastError = "Success. Delete in files app." - showErrorAlert.toggle() - } catch { - lastError = error.localizedDescription - showErrorAlert.toggle() - } + showTendiesImporter.toggle() }) { - Text("Apply") + Text("Select Tendies") + } + .buttonStyle(TintedButton(color: .green, fullwidth: true)) + + if let selectedTendies = selectedTendies { + HStack { + Text("Selected Tendies:") + .bold() + Spacer() + } + ForEach(selectedTendies, id: \.self) { tendie in + HStack { + Text("- \(tendie.deletingPathExtension().lastPathComponent)") + Spacer() + } + } + } + } + + Section { + if let selectedTendies = selectedTendies, pbHash != "" { + Button(action: { + do { + try PosterBoardManager.applyTendies(selectedTendies, appHash: pbHash) + lastError = "Please open PosterBoard in the Shortcuts app and then close it from the app switcher." + showSuccessAlert.toggle() + } catch { + lastError = error.localizedDescription + showErrorAlert.toggle() + } + }) { + Text("Apply") + } + .buttonStyle(TintedButton(color: .blue, fullwidth: true)) } } } + .padding() + .navigationTitle("Pocket Poster") } - .padding() - .fileImporter(isPresented: $showTendiesImporter, allowedContentTypes: [UTType(filenameExtension: "tendies", conformingTo: .data)!], onCompletion: { result in - switch result { - case .success(let url): - selectedTendie = url - case .failure(let error): - lastError = error.localizedDescription - showErrorAlert.toggle() - } - }) - .alert("Error", isPresented: $showErrorAlert) { - Button("OK") {} - } message: { - Text(lastError ?? "???") + .fileImporter(isPresented: $showTendiesImporter, allowedContentTypes: [UTType(filenameExtension: "tendies", conformingTo: .data)!], allowsMultipleSelection: true, onCompletion: { result in + switch result { + case .success(let url): + selectedTendies = url + case .failure(let error): + lastError = error.localizedDescription + showErrorAlert.toggle() } + }) + .alert("Error", isPresented: $showErrorAlert) { + Button("OK") {} + } message: { + Text(lastError ?? "???") + } + .alert("Success!", isPresented: $showSuccessAlert) { + Button("OK") {} + } message: { + Text(lastError ?? "???") + } } } diff --git a/Pocket-Poster-Info.plist b/Pocket-Poster-Info.plist new file mode 100644 index 0000000..ff579a6 --- /dev/null +++ b/Pocket-Poster-Info.plist @@ -0,0 +1,8 @@ + + + + + UIFileSharingEnabled + + +