diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index 428b032..b783356 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -575,7 +575,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 27; + CURRENT_PROJECT_VERSION = 28; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -593,7 +593,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.3.1; + MARKETING_VERSION = 3.3.2; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -610,7 +610,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 27; + CURRENT_PROJECT_VERSION = 28; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -628,7 +628,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.3.1; + MARKETING_VERSION = 3.3.2; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index 6221afe..8d4efee 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -84,12 +84,13 @@ struct InfoButton: View { Button(action: { self.isPopoverPresented.toggle() }) { - HStack { + HStack(alignment: .center, spacing: 5) { Image(systemName: "info.circle.fill") .resizable() .scaledToFit() .frame(width: 14, height: 14) .foregroundColor(color?.opacity(0.7) ?? Color("mode").opacity(0.7)) + .frame(height: 16) if !label!.isEmpty { Text(label!) .font(.callout) @@ -97,7 +98,6 @@ struct InfoButton: View { } } - } .buttonStyle(PlainButtonStyle()) .onHover { isHovered in diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index a89f8b9..45a2aa0 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -388,6 +388,23 @@ func removeApp(appState: AppState, withId id: UUID) { } +// Check if app bundle is nested +func isNested(path: URL) -> Bool { + let applicationsPath = "/Applications" + let homeApplicationsPath = "\(home)/Applications" + + guard path.path.contains("Applications") else { + return false + } + + // Get the parent directory of the app + let parentDirectory = path.deletingLastPathComponent().path + + // Check if the parent directory is not directly /Applications or ~/Applications + return parentDirectory != applicationsPath && parentDirectory != homeApplicationsPath +} + + // --- Extend Int to convert hours to seconds --- extension Int { diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index e03cdc6..cd0c4e7 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -273,17 +273,36 @@ struct FilesView: View { appState.currentView = .empty } } - var selectedItemsArray = Array(appState.selectedItems) - if let url = URL(string: appState.appInfo.path.absoluteString) { - let appFolderURL = appState.appInfo.path.absoluteString.contains("Wrapper") ? url.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() : url.deletingLastPathComponent() // Get the immediate parent directory of regular and wrapped apps - - if appFolderURL.path == "/Applications" || appFolderURL.path == "\(home)/Applications" { - // Do nothing, skip insertion - } else if appFolderURL.pathComponents.count > 2 && appFolderURL.path.contains("Applications") { - // Insert into selectedItemsArray only if there is an intermediary folder - selectedItemsArray.insert(appFolderURL, at: 0) - } - } + let selectedItemsArray = Array(appState.selectedItems) + + // Delete all folders above app bundle between /Applications and app bundle file + +// if let url = URL(string: appState.appInfo.path.absoluteString) { +// var parentURL = url.deletingLastPathComponent() // Immediate parent of the .app bundle +// +// // Traverse up the path components until just below /Applications or ~/Applications +// while parentURL.pathComponents.count > 2 && parentURL.path.contains("Applications") && parentURL.deletingLastPathComponent().path != "/Applications" && parentURL.deletingLastPathComponent().path != "\(home)/Applications" { +// parentURL = parentURL.deletingLastPathComponent() +// } +// +// // Now, parentURL should be the directory just below /Applications or ~/Applications +// if parentURL.path != "/Applications" && parentURL.path != "\(home)/Applications" { +// selectedItemsArray.insert(parentURL, at: 0) // Add the correct parent directory to the array +// } +// } + + // Delete folder only 1 up from app bundle between /Applications and app bundle file + +// if let url = URL(string: appState.appInfo.path.absoluteString) { +// let appFolderURL = appState.appInfo.path.absoluteString.contains("Wrapper") ? url.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() : url.deletingLastPathComponent() // Get the immediate parent directory of regular and wrapped apps +// +// if appFolderURL.path == "/Applications" || appFolderURL.path == "\(home)/Applications" { +// // Do nothing, skip insertion +// } else if appFolderURL.pathComponents.count > 2 && appFolderURL.path.contains("Applications") { +// // Insert into selectedItemsArray only if there is an intermediary folder +// selectedItemsArray.insert(appFolderURL, at: 0) +// } +// } killApp(appId: appState.appInfo.bundleIdentifier) { moveFilesToTrash(at: selectedItemsArray) { @@ -303,8 +322,8 @@ struct FilesView: View { caskCleanup(app: appState.appInfo.appName) } - // Remove app from app list if all app files were removed - if appState.appInfo.files.count == selectedItemsArray.count { + // Remove app from app list if main app bundle is removed + if selectedItemsArray.contains(where: { $0.absoluteString == appState.appInfo.path.absoluteString }) { removeApp(appState: appState, withId: appState.appInfo.id) } else { // Add deleted appInfo object to trashed array @@ -317,7 +336,6 @@ struct FilesView: View { appState.appInfoStore[index] = .empty } } - appState.appInfo = AppInfo.empty } } @@ -382,11 +400,17 @@ struct FileDetailsItem: View { } VStack(alignment: .leading, spacing: 5) { - Text(path.lastPathComponent) - .font(.title3) - .lineLimit(1) - .truncationMode(.tail) - .help(path.lastPathComponent) + HStack(alignment: .center) { + Text(path.lastPathComponent) + .font(.title3) + .lineLimit(1) + .truncationMode(.tail) + .help(path.lastPathComponent) + if isNested(path: path) { + InfoButton(text: "Application file is nested within subdirectories. To prevent deleting incorrect folders, Pearcleaner will leave these alone. You may manually delete the remaining folders if required.", color: nil, label: "") + } + } + Text(path.path) .font(.footnote) .lineLimit(1)