mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 08:29:07 +00:00
v1.8
This commit is contained in:
@@ -535,7 +535,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 8;
|
CURRENT_PROJECT_VERSION = 9;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BK8443AXLU;
|
DEVELOPMENT_TEAM = BK8443AXLU;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -552,7 +552,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.7;
|
MARKETING_VERSION = 1.8;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
|
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -569,7 +569,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 8;
|
CURRENT_PROJECT_VERSION = 9;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BK8443AXLU;
|
DEVELOPMENT_TEAM = BK8443AXLU;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -586,7 +586,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.7;
|
MARKETING_VERSION = 1.8;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
|
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -39,6 +39,16 @@ struct AppCommands: Commands {
|
|||||||
}
|
}
|
||||||
.keyboardShortcut("r", modifiers: .command)
|
.keyboardShortcut("r", modifiers: .command)
|
||||||
|
|
||||||
|
Button {
|
||||||
|
uninstallPearcleaner(appState: appState)
|
||||||
|
} label: {
|
||||||
|
Text("Uninstall Pearcleaner")
|
||||||
|
}
|
||||||
|
// .keyboardShortcut("C", modifiers: .command)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ class AppState: ObservableObject
|
|||||||
appName: "",
|
appName: "",
|
||||||
appVersion: "",
|
appVersion: "",
|
||||||
appIcon: nil,
|
appIcon: nil,
|
||||||
webApp: false
|
webApp: false,
|
||||||
|
wrapped: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,8 +53,9 @@ struct AppInfo: Identifiable, Hashable {
|
|||||||
let appVersion: String
|
let appVersion: String
|
||||||
let appIcon: NSImage?
|
let appIcon: NSImage?
|
||||||
let webApp: Bool
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
|
|||||||
var appIcon: NSImage?
|
var appIcon: NSImage?
|
||||||
var appName: String?
|
var appName: String?
|
||||||
var webApp: Bool?
|
var webApp: Bool?
|
||||||
|
|
||||||
if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String {
|
if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String {
|
||||||
appName = localizedName
|
appName = localizedName
|
||||||
} else if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String {
|
} else if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String {
|
||||||
@@ -120,7 +121,84 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
|
|||||||
webApp = false
|
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 {
|
} else {
|
||||||
print("One or more variables missing for app at path: \(path)")
|
print("One or more variables missing for app at path: \(path)")
|
||||||
|
|||||||
@@ -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 ---
|
// --- Create Application Support folder if it doesn't exist ---
|
||||||
func ensureApplicationSupportFolderExists(appState: AppState) {
|
func ensureApplicationSupportFolderExists(appState: AppState) {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ struct AppListItems: View {
|
|||||||
.background(Color("mode").opacity(0.1))
|
.background(Color("mode").opacity(0.1))
|
||||||
.clipShape(.capsule)
|
.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()
|
Spacer()
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ struct AppListView: View {
|
|||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
if appState.currentView == .empty {
|
if appState.currentView == .empty {
|
||||||
TopBar(reload: $reload)
|
TopBar(reload: $reload)
|
||||||
AppDetailsEmptyView()
|
AppDetailsEmptyView(showPopover: $showPopover)
|
||||||
} else if appState.currentView == .files {
|
} else if appState.currentView == .files {
|
||||||
TopBar(reload: $reload)
|
TopBar(reload: $reload)
|
||||||
FilesView(showPopover: $showPopover)
|
FilesView(showPopover: $showPopover)
|
||||||
@@ -184,13 +184,14 @@ struct AppDetailsEmptyView: View {
|
|||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@State private var animateGradient: Bool = false
|
@State private var animateGradient: Bool = false
|
||||||
|
@Binding var showPopover: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack() {
|
VStack() {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
DropTarget(appState: appState)
|
DropTarget(appState: appState, showPopover: $showPopover)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ struct DropTarget: View, DropDelegate {
|
|||||||
private var types: [UTType] = [.fileURL]
|
private var types: [UTType] = [.fileURL]
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@State private var phase: CGFloat = 4
|
@State private var phase: CGFloat = 4
|
||||||
|
@Binding var showPopover: Bool
|
||||||
|
|
||||||
public init(appState: AppState) {
|
public init(appState: AppState, showPopover: Binding<Bool>) {
|
||||||
self.appState = appState
|
self.appState = appState
|
||||||
|
_showPopover = showPopover
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -126,8 +128,12 @@ struct DropTarget: View, DropDelegate {
|
|||||||
self.ants = false
|
self.ants = false
|
||||||
appState.appInfo = appInfo!
|
appState.appInfo = appInfo!
|
||||||
findPathsForApp(appState: appState, appInfo: appState.appInfo)
|
findPathsForApp(appState: appState, appInfo: appState.appInfo)
|
||||||
|
if mini {
|
||||||
|
showPopover = true
|
||||||
|
} else {
|
||||||
appState.currentView = .files
|
appState.currentView = .files
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
print("Error: Dropped file is not an application bundle")
|
print("Error: Dropped file is not an application bundle")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|||||||
@@ -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) {
|
killApp(appId: appState.appInfo.bundleIdentifier) {
|
||||||
moveFilesToTrash(at: selectedItemsArray) {
|
moveFilesToTrash(at: selectedItemsArray) {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@@ -259,7 +264,14 @@ struct FileDetailsItem: View {
|
|||||||
let icon: Image?
|
let icon: Image?
|
||||||
let path: URL
|
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 {
|
var body: some View {
|
||||||
|
|
||||||
HStack(alignment: .center, spacing: 20) {
|
HStack(alignment: .center, spacing: 20) {
|
||||||
Toggle("", isOn: Binding(
|
Toggle("", isOn: Binding(
|
||||||
get: { self.appState.selectedItems.contains(self.path) },
|
get: { self.appState.selectedItems.contains(self.path) },
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ struct MiniMode: View {
|
|||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
if appState.currentView == .empty {
|
if appState.currentView == .empty {
|
||||||
TopBarMini(reload: $reload, search: $search)
|
TopBarMini(reload: $reload, search: $search)
|
||||||
MiniEmptyView()
|
MiniEmptyView(showPopover: $showPopover)
|
||||||
} else if appState.currentView == .files {
|
} else if appState.currentView == .files {
|
||||||
TopBarMini(reload: $reload, search: $search)
|
TopBarMini(reload: $reload, search: $search)
|
||||||
FilesView(showPopover: $showPopover)
|
FilesView(showPopover: $showPopover)
|
||||||
@@ -98,13 +98,14 @@ struct MiniEmptyView: View {
|
|||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@State private var animateGradient: Bool = false
|
@State private var animateGradient: Bool = false
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
|
@Binding var showPopover: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack() {
|
VStack() {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
DropTarget(appState: appState)
|
DropTarget(appState: appState, showPopover: $showPopover)
|
||||||
.frame(maxWidth: mini ? 300 : 500)
|
.frame(maxWidth: mini ? 300 : 500)
|
||||||
|
|
||||||
Text("Drop an app to begin")
|
Text("Drop an app to begin")
|
||||||
|
|||||||
Reference in New Issue
Block a user