This commit is contained in:
Alin
2023-12-08 13:17:46 -07:00
parent f79864dfc4
commit 67ced2328d
11 changed files with 176 additions and 25 deletions
+4 -4
View File
@@ -535,7 +535,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 8;
CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES;
@@ -552,7 +552,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.7;
MARKETING_VERSION = 1.8;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -569,7 +569,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 8;
CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES;
@@ -586,7 +586,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.7;
MARKETING_VERSION = 1.8;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
+10
View File
@@ -39,6 +39,16 @@ struct AppCommands: Commands {
}
.keyboardShortcut("r", modifiers: .command)
Button {
uninstallPearcleaner(appState: appState)
} label: {
Text("Uninstall Pearcleaner")
}
// .keyboardShortcut("C", modifiers: .command)
}
+4 -2
View File
@@ -39,7 +39,8 @@ class AppState: ObservableObject
appName: "",
appVersion: "",
appIcon: nil,
webApp: false
webApp: false,
wrapped: false
)
}
}
@@ -52,8 +53,9 @@ struct AppInfo: Identifiable, Hashable {
let appVersion: String
let appIcon: NSImage?
let webApp: Bool
let wrapped: Bool
static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil, webApp: false)
static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil, webApp: false, wrapped: false)
}
+79 -1
View File
@@ -78,6 +78,7 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
var appIcon: NSImage?
var appName: String?
var webApp: Bool?
if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String {
appName = localizedName
} else if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String {
@@ -120,7 +121,84 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
webApp = false
}
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName ?? "", appVersion: appVersion, appIcon: appIcon, webApp: webApp ?? false)
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName ?? "", appVersion: appVersion, appIcon: appIcon, webApp: webApp ?? false, wrapped: false)
} else {
// print("One or more variables missing for app at path: \(path)")
let wrapperURL = path.appendingPathComponent("Wrapper").appendingPathComponent(path.lastPathComponent)
if let wrappedAppInfo = getWrappedAppInfo(atPath: wrapperURL) {
return wrappedAppInfo
}
}
} else {
print("Bundle not found at path: \(path)")
}
return nil
}
func getWrappedAppInfo(atPath path: URL) -> AppInfo? {
if let bundle = Bundle(url: path) {
if let bundleIdentifier = bundle.bundleIdentifier,
let appVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String {
var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String
var appIcon: NSImage?
var appName: String?
var webApp: Bool?
if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String {
appName = localizedName
} else if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String {
appName = bundleName
} else {
appName = path.deletingPathExtension().lastPathComponent
}
if appIconFileName == nil {
if let iconsDict = bundle.infoDictionary?["CFBundleIcons"] as? [String: Any] {
if let primaryIconDict = iconsDict["CFBundlePrimaryIcon"] as? [String: Any],
let iconFiles = primaryIconDict["CFBundleIconFiles"] as? [String],
let primaryIconFile = iconFiles.first {
// Now, you can use the primaryIconFile to construct the path to the icon file
let iconPath = path.appendingPathComponent(primaryIconFile)
appIconFileName = iconPath.path
if let contents = try? FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil) {
// Find the first file that matches the specified prefix
if let foundURL = contents.first(where: { $0.lastPathComponent.hasPrefix(primaryIconFile) }),
let image = NSImage(contentsOfFile: foundURL.path) {
appIcon = image
} else {
print("No matching image found for \(primaryIconFile).")
}
} else {
print("Unable to access the directory at \(primaryIconFile).")
}
}
}
}
// Convert the icon to a 100x100 PNG image
if let pngIcon = appIcon.flatMap({ convertICNSToPNG(icon: $0, size: NSSize(width: 100, height: 100)) }) {
appIcon = pngIcon
}
if appIcon == nil {
print("App Icon not found for app at path: \(path)")
}
if bundle.infoDictionary?["LSTemplateApplication"] is Bool {
webApp = true
} else {
webApp = false
}
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName ?? "", appVersion: appVersion, appIcon: appIcon, webApp: webApp ?? false, wrapped: true)
} else {
print("One or more variables missing for app at path: \(path)")
+32
View File
@@ -394,6 +394,38 @@ func presentAlert(appState: AppState) -> Alert {
// --- Pearcleaner Uninstall --
func uninstallPearcleaner(appState: AppState) {
// Unload Sentinel Monitor if running
launchctl(load: false)
// Get app info for Pearcleaner
let appInfo = getAppInfo(atPath: Bundle.main.bundleURL)
// Find application files for Pearcleaner
findPathsForApp(appState: appState, appInfo: appInfo!)
// Kill Pearcleaner and tell Finder to trash the files
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
let selectedItemsArray = Array(appState.selectedItems).filter { !$0.path.contains(".Trash") }
let posixFiles = selectedItemsArray.map { "POSIX file \"\($0.path)\", " }.joined().dropLast(3)
let scriptSource = """
tell application \"Finder\" to delete { \(posixFiles)" }
"""
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "sleep 1; osascript -e '\(scriptSource)'"]
task.launch()
NSApp.terminate(nil)
exit(0)
}
}
// --- Create Application Support folder if it doesn't exist ---
func ensureApplicationSupportFolderExists(appState: AppState) {
let fileManager = FileManager.default
+9
View File
@@ -48,6 +48,15 @@ struct AppListItems: View {
.background(Color("mode").opacity(0.1))
.clipShape(.capsule)
}
if appInfo.wrapped {
Text("iOS")
.font(.footnote)
.foregroundStyle(Color("mode").opacity(0.5))
.frame(minWidth: 30, minHeight: 15)
.padding(2)
.background(Color("mode").opacity(0.1))
.clipShape(.capsule)
}
Spacer()
+3 -2
View File
@@ -133,7 +133,7 @@ struct AppListView: View {
VStack(spacing: 0) {
if appState.currentView == .empty {
TopBar(reload: $reload)
AppDetailsEmptyView()
AppDetailsEmptyView(showPopover: $showPopover)
} else if appState.currentView == .files {
TopBar(reload: $reload)
FilesView(showPopover: $showPopover)
@@ -184,13 +184,14 @@ struct AppDetailsEmptyView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var appState: AppState
@State private var animateGradient: Bool = false
@Binding var showPopover: Bool
var body: some View {
VStack() {
Spacer()
DropTarget(appState: appState)
DropTarget(appState: appState, showPopover: $showPopover)
Spacer()
+8 -2
View File
@@ -17,9 +17,11 @@ struct DropTarget: View, DropDelegate {
private var types: [UTType] = [.fileURL]
@Environment(\.colorScheme) var colorScheme
@State private var phase: CGFloat = 4
@Binding var showPopover: Bool
public init(appState: AppState) {
public init(appState: AppState, showPopover: Binding<Bool>) {
self.appState = appState
_showPopover = showPopover
}
var body: some View {
@@ -126,7 +128,11 @@ struct DropTarget: View, DropDelegate {
self.ants = false
appState.appInfo = appInfo!
findPathsForApp(appState: appState, appInfo: appState.appInfo)
appState.currentView = .files
if mini {
showPopover = true
} else {
appState.currentView = .files
}
}
} else {
print("Error: Dropped file is not an application bundle")
+13 -1
View File
@@ -170,7 +170,12 @@ struct FilesView: View {
}
}
let selectedItemsArray = Array(appState.selectedItems).filter { !$0.path.contains(".Trash") }
let selectedItemsArray = Array(appState.selectedItems)
.filter { !$0.path.contains(".Trash") }
.map { path in
return path.path.contains("Wrapper") ? path.deletingLastPathComponent().deletingLastPathComponent() : path
}
killApp(appId: appState.appInfo.bundleIdentifier) {
moveFilesToTrash(at: selectedItemsArray) {
withAnimation {
@@ -259,7 +264,14 @@ struct FileDetailsItem: View {
let icon: Image?
let path: URL
// init(size: String, icon: Image?, path: URL) {
// self.size = size
// self.icon = icon
// self.path = path.path.contains("Wrapper") ? path.deletingLastPathComponent().deletingLastPathComponent() : path
// }
var body: some View {
HStack(alignment: .center, spacing: 20) {
Toggle("", isOn: Binding(
get: { self.appState.selectedItems.contains(self.path) },
+3 -2
View File
@@ -27,7 +27,7 @@ struct MiniMode: View {
VStack(spacing: 0) {
if appState.currentView == .empty {
TopBarMini(reload: $reload, search: $search)
MiniEmptyView()
MiniEmptyView(showPopover: $showPopover)
} else if appState.currentView == .files {
TopBarMini(reload: $reload, search: $search)
FilesView(showPopover: $showPopover)
@@ -98,13 +98,14 @@ struct MiniEmptyView: View {
@EnvironmentObject var appState: AppState
@State private var animateGradient: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false
@Binding var showPopover: Bool
var body: some View {
VStack() {
Spacer()
DropTarget(appState: appState)
DropTarget(appState: appState, showPopover: $showPopover)
.frame(maxWidth: mini ? 300 : 500)
Text("Drop an app to begin")