This commit is contained in:
Alin
2024-08-01 16:47:34 -06:00
parent 2da3da9ce7
commit e0d41e76f4
17 changed files with 356 additions and 231 deletions
@@ -1,5 +1,5 @@
{ {
"originHash" : "284df40ab461728f91e9fff8f6afbaf9f1121ce265b549a5187092a1097d4db1", "originHash" : "bebe4a176f4f3b9e625a60cb3048363d2aeb03aa06dabf97821c3b73b1c5f149",
"pins" : [ "pins" : [
{ {
"identity" : "alinfoundation", "identity" : "alinfoundation",
@@ -7,7 +7,7 @@
"location" : "https://github.com/alienator88/AlinFoundation", "location" : "https://github.com/alienator88/AlinFoundation",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "d660e9a1fb07ec3fa036c89c6f9fc1d573cecf9e" "revision" : "986ac354d2a01f87ed4c37d19ce80a752a62fd4c"
} }
}, },
{ {
+6 -6
View File
@@ -15,18 +15,18 @@ class AppPathFinder {
private var appState: AppState private var appState: AppState
private var locations: Locations private var locations: Locations
private var backgroundRun: Bool private var backgroundRun: Bool
private var reverseAddon: Bool // private var reverseAddon: Bool
private var undo: Bool private var undo: Bool
private var completion: () -> Void = {} private var completion: () -> Void = {}
private var collection: [URL] = [] private var collection: [URL] = []
private let collectionAccessQueue = DispatchQueue(label: "com.alienator88.Pearcleaner.appPathFinder.collectionAccess") private let collectionAccessQueue = DispatchQueue(label: "com.alienator88.Pearcleaner.appPathFinder.collectionAccess")
init(appInfo: AppInfo = .empty, appState: AppState, locations: Locations, backgroundRun: Bool = false, reverseAddon: Bool = false, undo: Bool = false, completion: @escaping () -> Void = {}) { init(appInfo: AppInfo = .empty, appState: AppState, locations: Locations, backgroundRun: Bool = false, undo: Bool = false, completion: @escaping () -> Void = {}) {
self.appInfo = appInfo self.appInfo = appInfo
self.appState = appState self.appState = appState
self.locations = locations self.locations = locations
self.backgroundRun = backgroundRun self.backgroundRun = backgroundRun
self.reverseAddon = reverseAddon // self.reverseAddon = reverseAddon
self.undo = undo self.undo = undo
self.completion = completion self.completion = completion
} }
@@ -338,9 +338,9 @@ class AppPathFinder {
} }
// Append object to store if running reverse search with empty store // Append object to store if running reverse search with empty store
if self.reverseAddon { // if self.reverseAddon {
self.appState.appInfoStore.append(self.appInfo) // self.appState.appInfoStore.append(self.appInfo)
} // }
self.completion() self.completion()
} }
+13 -1
View File
@@ -13,7 +13,7 @@ let home = FileManager.default.homeDirectoryForCurrentUser.path
class AppState: ObservableObject { class AppState: ObservableObject {
@Published var appInfo: AppInfo @Published var appInfo: AppInfo
@Published var appInfoStore: [AppInfo] = [] // @Published var appInfoStore: [AppInfo] = []
@Published var trashedFiles: [AppInfo] = [] @Published var trashedFiles: [AppInfo] = []
@Published var zombieFile: ZombieFile @Published var zombieFile: ZombieFile
@Published var sortedApps: [AppInfo] = [] @Published var sortedApps: [AppInfo] = []
@@ -23,12 +23,24 @@ class AppState: ObservableObject {
@Published var sidebar: Bool = true @Published var sidebar: Bool = true
@Published var reload: Bool = false @Published var reload: Bool = false
@Published var showProgress: Bool = false @Published var showProgress: Bool = false
@Published var leftoverProgress: (String, Double) = ("", 0.0)
@Published var finderExtensionEnabled: Bool = false @Published var finderExtensionEnabled: Bool = false
@Published var showUninstallAlert: Bool = false @Published var showUninstallAlert: Bool = false
@Published var oneShotMode: Bool = false @Published var oneShotMode: Bool = false
@Published var showConditionBuilder: Bool = false @Published var showConditionBuilder: Bool = false
var operationQueueLeftover = OperationQueue()
@Published var shouldCancelOperations = false
func cancelQueueOperations() {
operationQueueLeftover.cancelAllOperations()
shouldCancelOperations = true
DispatchQueue.main.async {
self.leftoverProgress = ("Search canceled", 0.0)
self.showProgress = false
self.currentView = .empty
}
}
init() { init() {
self.appInfo = AppInfo( self.appInfo = AppInfo(
+25 -13
View File
@@ -52,53 +52,62 @@ var conditions: [Condition] = [
Condition( Condition(
bundle_id: "com.robotsandpencils.xcodesapp", bundle_id: "com.robotsandpencils.xcodesapp",
include: [], include: [],
exclude: ["com.apple.dt.xcode", "com.oneminutegames.xcodecleaner", "io.hyperapp.xcodecleaner"] exclude: ["com.apple.dt.xcode", "com.oneminutegames.xcodecleaner", "io.hyperapp.xcodecleaner"],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "io.hyperapp.xcodecleaner", bundle_id: "io.hyperapp.xcodecleaner",
include: [], include: [],
exclude: ["com.robotsandpencils.xcodesapp", "com.oneminutegames.xcodecleaner", "com.apple.dt.xcode", "xcodes.json"] exclude: ["com.robotsandpencils.xcodesapp", "com.oneminutegames.xcodecleaner", "com.apple.dt.xcode", "xcodes.json"],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "us.zoom.xos", bundle_id: "us.zoom.xos",
include: ["zoom"], include: ["zoom"],
exclude: [] exclude: [],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "com.brave.browser", bundle_id: "com.brave.browser",
include: ["brave"], include: ["brave"],
exclude: [] exclude: [],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "com.okta.mobile", bundle_id: "com.okta.mobile",
include: ["okta"], include: ["okta"],
exclude: [] exclude: [],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "com.google.chrome", bundle_id: "com.google.chrome",
include: ["google", "chrome"], include: ["google", "chrome"],
exclude: ["iterm", "chromefeaturestate"] exclude: ["iterm", "chromefeaturestate"],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "com.microsoft.edgemac", bundle_id: "com.microsoft.edgemac",
include: ["microsoft"], include: ["microsoft"],
exclude: ["vscode", "rdc", "appcenter", "office", "oneauth"] exclude: ["vscode", "rdc", "appcenter", "office", "oneauth"],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "org.mozilla.firefox", bundle_id: "org.mozilla.firefox",
include: ["mozilla", "firefox"], include: ["mozilla", "firefox"],
exclude: [] exclude: [],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "org.mozilla.firefox.nightly", bundle_id: "org.mozilla.firefox.nightly",
include: ["mozilla", "firefox"], include: ["mozilla", "firefox"],
exclude: [] exclude: [],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "com.logi.optionsplus", bundle_id: "com.logi.optionsplus",
include: ["logi"], include: ["logi"],
exclude: ["login", "logic"], exclude: ["login", "logic"],
includeForce: [] includeForce: nil
), ),
Condition( Condition(
bundle_id: "com.microsoft.vscode", bundle_id: "com.microsoft.vscode",
@@ -109,17 +118,20 @@ var conditions: [Condition] = [
Condition( Condition(
bundle_id: "com.facebook.archon.developerid", bundle_id: "com.facebook.archon.developerid",
include: ["archon.loginhelper"], include: ["archon.loginhelper"],
exclude: [] exclude: [],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "eu.exelban.stats", bundle_id: "eu.exelban.stats",
include: [], include: [],
exclude: ["video"] exclude: ["video"],
includeForce: nil
), ),
Condition( Condition(
bundle_id: "jetbrains", bundle_id: "jetbrains",
include: ["jetbrains", "jcef"], include: ["jetbrains", "jcef"],
exclude: [] exclude: [],
includeForce: nil
), ),
] ]
+142 -60
View File
@@ -109,45 +109,108 @@ func listAppSupportDirectories() -> [String] {
// Load app paths on launch // Load app paths on launch
func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locations, fsm: FolderSettingsManager, reverseAddon: Bool = false, completion: @escaping () -> Void = {}) { func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locations, fsm: FolderSettingsManager, completion: @escaping () -> Void = {}) {
let dispatchGroup = DispatchGroup() // appState.operationQueueLeftover.maxConcurrentOperationCount = 10 // Adjust this value as needed
appState.appInfoStore.removeAll() // appState.shouldCancelOperations = false
// appState.appInfoStore.removeAll()
// let sortedAllApps = allApps.sorted { $0.appName.localizedCompare($1.appName) == .orderedAscending }
for app in allApps { updateOnMain {
dispatchGroup.enter() appState.leftoverProgress.0 = "Finding leftover files, please wait..."
DispatchQueue.global(qos: .background).async {
AppPathFinder(appInfo: app, appState: appState, locations: locations, backgroundRun: true, reverseAddon: reverseAddon).findPaths()
dispatchGroup.leave()
}
} }
ReversePathsSearcher(appState: appState, locations: locations, fsm: fsm, sortedApps: allApps).reversePathsSearch {
dispatchGroup.notify(queue: DispatchQueue.global(qos: .background)) { updateOnMain {
printOS("Reverse search processed successfully")
func checkAllAppsProcessed(retryCount: Int = 0, maxRetry: Int = 120) { appState.showProgress = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { withAnimation {
if appState.appInfoStore.count == allApps.count { appState.leftoverProgress.1 = 0.0
ReversePathsSearcher(appState: appState, locations: locations, fsm: fsm).reversePathsSearch() {
updateOnMain {
appState.showProgress = false
}
}
completion()
} else if retryCount < maxRetry {
checkAllAppsProcessed(retryCount: retryCount + 1, maxRetry: maxRetry)
} else {
// Reset progress values to 0
updateOnMain {
appState.showProgress = false
}
printOS("loadAllPaths - Retry limit reached. Not all paths were loaded.")
completion()
}
} }
appState.leftoverProgress.0 = "Reverse search completed successfully"
} }
completion()
checkAllAppsProcessed()
} }
// let totalApps = sortedAllApps.count
// var processedApps = 0
//
// DispatchQueue.global(qos: .userInitiated).async {
// for (index, app) in sortedAllApps.enumerated() {
// autoreleasepool {
// if appState.shouldCancelOperations {
// printOS("Operations cancelled.") // Debug print
// return
// }
// printOS("Processing app \(index + 1) of \(totalApps): \(app.appName)")
//
// let semaphore = DispatchSemaphore(value: 0)
// appState.operationQueueLeftover.addOperation {
// if appState.shouldCancelOperations {
// printOS("Operations cancelled.") // Debug print
// return
// }
// let pathFinder = AppPathFinder(
// appInfo: app,
// appState: appState,
// locations: locations,
// backgroundRun: true,
// reverseAddon: reverseAddon,
// completion: {
// semaphore.signal()
// }
// )
// pathFinder.findPaths()
// }
//
// semaphore.wait()
//
// DispatchQueue.main.async {
// processedApps += 1
// let progress = Double(processedApps) / Double(totalApps)
// withAnimation {
// appState.leftoverProgress.1 = progress
// }
// appState.leftoverProgress.0 = "Excluding application files for \(app.appName)"
// printOS("Progress: \(Int(progress * 100))%") // Debug print
// }
// }
//
// }
//
// appState.operationQueueLeftover.waitUntilAllOperationsAreFinished()
//
// DispatchQueue.main.async {
// if appState.shouldCancelOperations {
// completion()
// return
// }
//
// if appState.appInfoStore.count == sortedAllApps.count {
// printOS("All apps processed successfully")
// appState.leftoverProgress.0 = "Finding leftover files, please wait..."
// ReversePathsSearcher(appState: appState, locations: locations, fsm: fsm, sortedApps: sortedAllApps).reversePathsSearch {
// updateOnMain {
// printOS("Reverse search processed successfully")
// appState.showProgress = false
// withAnimation {
// appState.leftoverProgress.1 = 0.0
// }
// appState.leftoverProgress.0 = "Reverse search completed successfully"
// }
// completion()
// }
// } else {
// printOS("reversePreloader - Not all paths were loaded. Expected: \(sortedAllApps.count), Actual: \(appState.appInfoStore.count)")
// updateOnMain {
// appState.showProgress = false
// withAnimation {
// appState.leftoverProgress.1 = 0.0
// }
// appState.leftoverProgress.0 = "Reverse search failed to process existing application files (\(sortedAllApps.count)/\(appState.appInfoStore.count))"
// }
// completion()
// }
// }
// }
} }
@@ -161,37 +224,56 @@ func showAppInFiles(appInfo: AppInfo, appState: AppState, locations: Locations,
appState.appInfo = .empty appState.appInfo = .empty
appState.selectedItems = [] appState.selectedItems = []
// Check if the appInfo exists in the appState.appInfoStore // When the appInfo is not found, show progress, and search for paths.
if let storedAppInfo = appState.appInfoStore.first(where: { $0.path == appInfo.path }) { appState.showProgress = true
// Update appState with the stored app info and selected items.
appState.appInfo = storedAppInfo
appState.selectedItems = Set(storedAppInfo.files)
// Trigger the animation for changing views and showing the popover. // Initialize the path finder and execute its search.
withAnimation(Animation.easeIn(duration: 0.4)) { AppPathFinder(appInfo: appInfo, appState: appState, locations: locations) {
appState.currentView = .files updateOnMain {
showPopover.wrappedValue.toggle() // Update the progress indicator on the main thread once the search completes.
appState.showProgress = false
} }
} else { }.findPaths()
// When the appInfo is not found, show progress, and search for paths.
appState.showProgress = true
// Initialize the path finder and execute its search. appState.appInfo = appInfo
AppPathFinder(appInfo: appInfo, appState: appState, locations: locations) {
updateOnMain {
// Update the progress indicator on the main thread once the search completes.
appState.showProgress = false
}
}.findPaths()
appState.appInfo = appInfo // Animate the view change and popover display.
withAnimation(Animation.easeIn(duration: 0.4)) {
// Animate the view change and popover display. appState.currentView = .files
withAnimation(Animation.easeIn(duration: 0.4)) { showPopover.wrappedValue.toggle()
appState.currentView = .files
showPopover.wrappedValue.toggle()
}
} }
// Check if the appInfo exists in the appState.appInfoStore
// if let storedAppInfo = appState.appInfoStore.first(where: { $0.path == appInfo.path }) {
// // Update appState with the stored app info and selected items.
// appState.appInfo = storedAppInfo
// appState.selectedItems = Set(storedAppInfo.files)
//
// // Trigger the animation for changing views and showing the popover.
// withAnimation(Animation.easeIn(duration: 0.4)) {
// appState.currentView = .files
// showPopover.wrappedValue.toggle()
// }
// } else {
// // When the appInfo is not found, show progress, and search for paths.
// appState.showProgress = true
//
// // Initialize the path finder and execute its search.
// AppPathFinder(appInfo: appInfo, appState: appState, locations: locations) {
// updateOnMain {
// // Update the progress indicator on the main thread once the search completes.
// appState.showProgress = false
// }
// }.findPaths()
//
// appState.appInfo = appInfo
//
// // Animate the view change and popover display.
// withAnimation(Animation.easeIn(duration: 0.4)) {
// appState.currentView = .files
// showPopover.wrappedValue.toggle()
// }
// }
} }
} }
+40 -15
View File
@@ -19,15 +19,15 @@ class ReversePathsSearcher {
private var fileSizeLogical: [URL: Int64] = [:] private var fileSizeLogical: [URL: Int64] = [:]
private var fileIcon: [URL: NSImage?] = [:] private var fileIcon: [URL: NSImage?] = [:]
private let dispatchGroup = DispatchGroup() private let dispatchGroup = DispatchGroup()
private let sortedApps: [AppInfo]
init(appState: AppState, locations: Locations, fsm: FolderSettingsManager) { init(appState: AppState, locations: Locations, fsm: FolderSettingsManager, sortedApps: [AppInfo]) {
self.appState = appState self.appState = appState
self.locations = locations self.locations = locations
self.fsm = fsm self.fsm = fsm
self.sortedApps = sortedApps
} }
func reversePathsSearch(completion: @escaping () -> Void = {}) { func reversePathsSearch(completion: @escaping () -> Void = {}) {
Task(priority: .high) { Task(priority: .high) {
self.processLocations() self.processLocations()
@@ -38,51 +38,74 @@ class ReversePathsSearcher {
} }
private func processLocations() { private func processLocations() {
let allPaths = appState.appInfoStore.flatMap { $0.fileSize.keys.map { $0.path.pearFormat() } }
let allNames = appState.appInfoStore.map { $0.appName.pearFormat() }
for location in locations.reverse.paths where fileManager.fileExists(atPath: location) { for location in locations.reverse.paths where fileManager.fileExists(atPath: location) {
dispatchGroup.enter() dispatchGroup.enter()
processLocation(location, allPaths: allPaths, allNames: allNames) processLocation(location)
dispatchGroup.leave() dispatchGroup.leave()
} }
} }
private func processLocation(_ location: String, allPaths: [String], allNames: [String]) { private func processLocation(_ location: String) {
do { do {
let contents = try fileManager.contentsOfDirectory(atPath: location) let contents = try fileManager.contentsOfDirectory(atPath: location)
contents.forEach { itemName in contents.forEach { itemName in
let itemURL = URL(fileURLWithPath: location).appendingPathComponent(itemName) let itemURL = URL(fileURLWithPath: location).appendingPathComponent(itemName)
processItem(itemName, itemURL: itemURL, allPaths: allPaths, allNames: allNames) processItem(itemName, itemURL: itemURL)
} }
} catch { } catch {
printOS("Error processing location: \(location), error: \(error)") printOS("Error processing location: \(location), error: \(error)")
} }
} }
private func processItem(_ itemName: String, itemURL: URL, allPaths: [String], allNames: [String]) { private func processItem(_ itemName: String, itemURL: URL) {
let formattedItemName = itemName.pearFormat()
let itemPath = itemURL.path.pearFormat() let itemPath = itemURL.path.pearFormat()
let itemLastPathComponent = itemURL.lastPathComponent.pearFormat()
let exclusionList = fsm.fileFolderPathsZ.map { $0.pearFormat() } let exclusionList = fsm.fileFolderPathsZ.map { $0.pearFormat() }
if exclusionList.contains(itemPath) || itemPath.contains("dsstore") || itemPath.contains("daemonnameoridentifierhere") { if exclusionList.contains(itemPath) || itemPath.contains("dsstore") || itemPath.contains("daemonnameoridentifierhere") {
return return
} }
guard !skipReverse.contains(where: { formattedItemName.contains($0) }), guard !skipReverse.contains(where: { itemName.pearFormat().contains($0) }),
!allPaths.contains(where: { $0 == itemPath || $0.hasSuffix("/\(itemLastPathComponent)") }), isSupportedFileType(at: itemURL.path),
!allNames.contains(formattedItemName), !isRelatedToInstalledApp(itemPath: itemPath),
isSupportedFileType(at: itemURL.path) else { !isExcludedByConditions(itemPath: itemPath) else {
return return
} }
collection.append(itemURL) collection.append(itemURL)
} }
private func isRelatedToInstalledApp(itemPath: String) -> Bool {
for app in sortedApps {
if itemPath.contains(app.bundleIdentifier.pearFormat()) || itemPath.contains(app.appName.pearFormat()) {
return true
}
}
return false
}
private func isExcludedByConditions(itemPath: String) -> Bool {
for condition in conditions {
// Include keywords
if condition.include.contains(where: { itemPath.contains($0.pearFormat()) }) {
return true
}
// Include force
if let includeForce = condition.includeForce,
includeForce.contains(where: { itemPath.contains($0.path.pearFormat()) }) {
return true
}
}
return false
}
private func calculateFileDetails() { private func calculateFileDetails() {
collection.forEach { path in collection.forEach { path in
let size = totalSizeOnDisk(for: path) let size = totalSizeOnDisk(for: path)
@@ -104,3 +127,5 @@ class ReversePathsSearcher {
} }
} }
+3 -3
View File
@@ -208,9 +208,9 @@ func removeApp(appState: AppState, withPath path: URL) {
// return // Exit the function if the app was found and removed // return // Exit the function if the app was found and removed
} }
// Remove from appInfoStore if found // Remove from appInfoStore if found
if let index = appState.appInfoStore.firstIndex(where: { $0.path == path }) { // if let index = appState.appInfoStore.firstIndex(where: { $0.path == path }) {
appState.appInfoStore.remove(at: index) // appState.appInfoStore.remove(at: index)
} // }
// Brew cleanup if enabled // Brew cleanup if enabled
if brew { if brew {
caskCleanup(app: appState.appInfo.appName) caskCleanup(app: appState.appInfo.appName)
+7 -16
View File
@@ -25,7 +25,6 @@ struct PearcleanerApp: App {
@AppStorage("settings.general.brew") private var brew: Bool = false @AppStorage("settings.general.brew") private var brew: Bool = false
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
@AppStorage("settings.menubar.mainWin") private var mainWinEnabled: Bool = false @AppStorage("settings.menubar.mainWin") private var mainWinEnabled: Bool = false
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "trash"
@State private var search = "" @State private var search = ""
@State private var showPopover: Bool = false @State private var showPopover: Bool = false
let conditionManager = ConditionManager.shared let conditionManager = ConditionManager.shared
@@ -34,11 +33,11 @@ struct PearcleanerApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
Group { Group {
if !mini { if mini {
RegularMode(search: $search, showPopover: $showPopover) MiniMode(search: $search, showPopover: $showPopover)
} else { } else {
MiniMode(search: $search, showPopover: $showPopover) RegularMode(search: $search, showPopover: $showPopover)
} }
} }
.environmentObject(appState) .environmentObject(appState)
.environmentObject(locations) .environmentObject(locations)
@@ -92,13 +91,13 @@ struct PearcleanerApp: App {
appState.currentView = .empty appState.currentView = .empty
} }
// Disable tabbing // Disable tabbing
NSWindow.allowsAutomaticWindowTabbing = false NSWindow.allowsAutomaticWindowTabbing = false
// Load apps list on startup // Load apps list on startup
reloadAppsList(appState: appState, fsm: fsm) reloadAppsList(appState: appState, fsm: fsm)
// Enable menubar item // Enable menubar item
if menubarEnabled { if menubarEnabled {
MenuBarExtraManager.shared.addMenuBarExtra(withView: { MenuBarExtraManager.shared.addMenuBarExtra(withView: {
@@ -110,19 +109,13 @@ struct PearcleanerApp: App {
.environmentObject(updater) .environmentObject(updater)
.environmentObject(permissionManager) .environmentObject(permissionManager)
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
}, icon: selectedMenubarIcon) })
} }
#if !DEBUG #if !DEBUG
Task { Task {
/// This will check for updates on load based on the update frequency
updater.checkAndUpdateIfNeeded()
/// Get new features
updater.checkForAnnouncement()
// Make sure App Support folder exists in the future if needed for storage // Make sure App Support folder exists in the future if needed for storage
// ensureApplicationSupportFolderExists(appState: appState) // ensureApplicationSupportFolderExists(appState: appState)
@@ -136,8 +129,6 @@ struct PearcleanerApp: App {
.windowResizability(.contentMinSize) .windowResizability(.contentMinSize)
.commands { .commands {
AppCommands(appState: appState, locations: locations, fsm: fsm, updater: updater, themeManager: themeManager) AppCommands(appState: appState, locations: locations, fsm: fsm, updater: updater, themeManager: themeManager)
// CommandGroup(replacing: .newItem, addition: { })
} }
-1
View File
@@ -14,7 +14,6 @@ struct GeneralSettingsTab: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations @EnvironmentObject var locations: Locations
@EnvironmentObject var themeManager: ThemeManager @EnvironmentObject var themeManager: ThemeManager
@State private var windowSettings = WindowSettings()
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
@AppStorage("settings.general.brew") private var brew: Bool = false @AppStorage("settings.general.brew") private var brew: Bool = false
@AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false @AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false
+6 -15
View File
@@ -24,7 +24,7 @@ struct InterfaceSettingsTab: View {
@AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.glass") private var glass: Bool = false
@AppStorage("settings.general.popover") private var popoverStay: Bool = true @AppStorage("settings.general.popover") private var popoverStay: Bool = true
@AppStorage("settings.general.miniview") private var miniView: Bool = true @AppStorage("settings.general.miniview") private var miniView: Bool = true
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "bubbles.and.sparkles" @AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "bubbles.and.sparkles.fill"
@State private var isLaunchAtLoginEnabled: Bool = false @State private var isLaunchAtLoginEnabled: Bool = false
@Binding var showPopover: Bool @Binding var showPopover: Bool
@Binding var search: String @Binding var search: String
@@ -143,9 +143,7 @@ struct InterfaceSettingsTab: View {
.environmentObject(updater) .environmentObject(updater)
.environmentObject(permissionManager) .environmentObject(permissionManager)
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
}) { })
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
}
} else { } else {
if appState.appInfo.appName.isEmpty { if appState.appInfo.appName.isEmpty {
appState.currentView = .empty appState.currentView = .empty
@@ -162,9 +160,7 @@ struct InterfaceSettingsTab: View {
.environmentObject(permissionManager) .environmentObject(permissionManager)
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
} }
) { )
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
}
} }
@@ -265,10 +261,9 @@ struct InterfaceSettingsTab: View {
.environmentObject(updater) .environmentObject(updater)
.environmentObject(permissionManager) .environmentObject(permissionManager)
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
}, icon: selectedMenubarIcon) })
NSApplication.shared.setActivationPolicy(.accessory) NSApplication.shared.setActivationPolicy(.accessory)
findAndHideWindows(named: ["Pearcleaner"]) findAndHideWindows(named: ["Pearcleaner"])
// findAndShowWindows(named: ["Pearcleaner", "Interface"])
} else { } else {
MenuBarExtraManager.shared.removeMenuBarExtra() MenuBarExtraManager.shared.removeMenuBarExtra()
NSApplication.shared.setActivationPolicy(.regular) NSApplication.shared.setActivationPolicy(.regular)
@@ -283,9 +278,7 @@ struct InterfaceSettingsTab: View {
.environmentObject(updater) .environmentObject(updater)
.environmentObject(permissionManager) .environmentObject(permissionManager)
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
}) { })
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
}
} else { } else {
windowSettings.newWindow(withView: { windowSettings.newWindow(withView: {
RegularMode(search: $search, showPopover: $showPopover) RegularMode(search: $search, showPopover: $showPopover)
@@ -296,9 +289,7 @@ struct InterfaceSettingsTab: View {
.environmentObject(updater) .environmentObject(updater)
.environmentObject(permissionManager) .environmentObject(permissionManager)
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
}) { })
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
}
} }
} }
+4 -8
View File
@@ -14,7 +14,6 @@ struct AppListItems: View {
@EnvironmentObject var themeManager: ThemeManager @EnvironmentObject var themeManager: ThemeManager
@Binding var search: String @Binding var search: String
@State private var isHovered = false @State private var isHovered = false
@State private var windowSettings = WindowSettings()
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@AppStorage("displayMode") var displayMode: DisplayMode = .system @AppStorage("displayMode") var displayMode: DisplayMode = .system
@AppStorage("settings.general.miniview") private var miniView: Bool = true @AppStorage("settings.general.miniview") private var miniView: Bool = true
@@ -79,13 +78,10 @@ struct AppListItems: View {
) )
} }
if bundleSize == 0 { Text(bundleSize == 0 ? "v\(appInfo.appVersion)" : (isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human))
ProgressView().controlSize(.mini).padding(.leading, 5) .font(.system(size: (isHovered || isSelected) ? 12 : 10))
} else { .foregroundStyle(.primary.opacity(0.5))
Text("\(isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human)")
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
.foregroundStyle(.primary.opacity(0.5))
}
} }
} }
+1 -1
View File
@@ -105,7 +105,7 @@ struct AppSearchView: View {
appState.currentView = .zombie appState.currentView = .zombie
appState.showProgress.toggle() appState.showProgress.toggle()
showPopover.toggle() showPopover.toggle()
reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm, reverseAddon: true) reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm)
} else { } else {
appState.currentView = .zombie appState.currentView = .zombie
showPopover.toggle() showPopover.toggle()
+1 -3
View File
@@ -46,13 +46,11 @@ struct SectionView: View {
@Binding var showPopover: Bool @Binding var showPopover: Bool
var body: some View { var body: some View {
VStack(spacing: 0) { LazyVStack(spacing: 0) {
Header(title: title, count: count, showPopover: $showPopover) Header(title: title, count: count, showPopover: $showPopover)
.padding(.leading, 5) .padding(.leading, 5)
ForEach(apps, id: \.self) { appInfo in ForEach(apps, id: \.self) { appInfo in
AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo)
if appInfo != apps.last {
}
} }
} }
} }
+34 -28
View File
@@ -24,9 +24,8 @@ struct FilesView: View {
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@Binding var showPopover: Bool @Binding var showPopover: Bool
@Binding var search: String @Binding var search: String
@State private var elapsedTime = 0 // @State private var elapsedTime = 0
@State private var timer: Timer? = nil // @State private var timer: Timer? = nil
@State private var isShowingInspector = false
var body: some View { var body: some View {
@@ -51,35 +50,42 @@ struct FilesView: View {
if appState.showProgress { if appState.showProgress {
VStack { VStack {
Spacer() Spacer()
Text("Searching the file system").font(.title3)
.foregroundStyle((.primary.opacity(0.5)))
ProgressView() HStack(spacing: 10) {
.progressViewStyle(.linear) Text("Searching the file system").font(.title3)
.frame(width: 400, height: 10) .foregroundStyle(.primary.opacity(0.5))
ProgressView().controlSize(.small)
}
Text("\(elapsedTime)") // Text("Searching the file system").font(.title3)
.font(.title).monospacedDigit() // .foregroundStyle((.primary.opacity(0.5)))
.foregroundStyle((.primary.opacity(0.5))) //
.opacity(elapsedTime == 0 ? 0 : 1) // ProgressView()
.contentTransition(.numericText()) // .progressViewStyle(.linear)
// .frame(width: 400, height: 10)
//
// Text("\(elapsedTime)")
// .font(.title).monospacedDigit()
// .foregroundStyle((.primary.opacity(0.5)))
// .opacity(elapsedTime == 0 ? 0 : 1)
// .contentTransition(.numericText())
Spacer() Spacer()
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(.opacity) .transition(.opacity)
.onAppear { // .onAppear {
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in // self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
withAnimation { // withAnimation {
self.elapsedTime += 1 // self.elapsedTime += 1
} // }
} // }
} // }
.onDisappear { // .onDisappear {
self.timer?.invalidate() // self.timer?.invalidate()
self.timer = nil // self.timer = nil
self.elapsedTime = 0 // self.elapsedTime = 0
} // }
} else { } else {
// Titlebar // Titlebar
HStack(spacing: 0) { HStack(spacing: 0) {
@@ -355,9 +361,9 @@ struct FilesView: View {
appState.trashedFiles.append(appState.appInfo) appState.trashedFiles.append(appState.appInfo)
// Clear out appInfoStore object (Used for leftover file search) // Clear out appInfoStore object (Used for leftover file search)
if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) { // if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) {
appState.appInfoStore[index] = .empty // appState.appInfoStore[index] = .empty
} // }
updateOnMain { updateOnMain {
// Remove items from the list // Remove items from the list
+6 -9
View File
@@ -13,21 +13,20 @@ class MenuBarExtraManager {
private var statusItem: NSStatusItem? private var statusItem: NSStatusItem?
private var popover = NSPopover() private var popover = NSPopover()
private var lastView: (() -> AnyView)? private var lastView: (() -> AnyView)?
private var lastIcon: String? @AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "bubbles.and.sparkles.fill"
func addMenuBarExtra<V: View>(withView view: @escaping () -> V, icon: String) { func addMenuBarExtra<V: View>(withView view: @escaping () -> V) {
guard statusItem == nil else { return } guard statusItem == nil else { return }
// Remember the last view and icon // Remember the last view
lastView = { AnyView(view()) } lastView = { AnyView(view()) }
lastIcon = icon
// Initialize the status item // Initialize the status item
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
// Set up the status item's button // Set up the status item's button
if let button = statusItem?.button{ if let button = statusItem?.button{
button.image = NSImage(systemSymbolName: icon, accessibilityDescription: "Pearcleaner") button.image = NSImage(systemSymbolName: selectedMenubarIcon, accessibilityDescription: "Pearcleaner")
button.action = #selector(togglePopover(_:)) button.action = #selector(togglePopover(_:))
button.target = self button.target = self
button.sendAction(on: [.leftMouseDown, .rightMouseDown]) button.sendAction(on: [.leftMouseDown, .rightMouseDown])
@@ -51,8 +50,6 @@ class MenuBarExtraManager {
guard let button = statusItem?.button else { return } guard let button = statusItem?.button else { return }
if let image = NSImage(systemSymbolName: icon, accessibilityDescription: nil) { if let image = NSImage(systemSymbolName: icon, accessibilityDescription: nil) {
button.image = image button.image = image
} else {
button.image = NSImage(named: icon)
} }
} }
@@ -61,8 +58,8 @@ class MenuBarExtraManager {
self.removeMenuBarExtra() self.removeMenuBarExtra()
// Ensure the last view and icon are available before re-adding // Ensure the last view and icon are available before re-adding
if let lastView = self.lastView, let lastIcon = self.lastIcon { if let lastView = self.lastView {
self.addMenuBarExtra(withView: lastView, icon: lastIcon) self.addMenuBarExtra(withView: lastView)
} }
} }
} }
+4 -22
View File
@@ -7,6 +7,8 @@
import SwiftUI import SwiftUI
import AlinFoundation import AlinFoundation
import Combine
class WindowSettings { class WindowSettings {
private let windowWidthKey = "windowWidthKey" private let windowWidthKey = "windowWidthKey"
@@ -76,7 +78,7 @@ class WindowSettings {
} }
// Launch new app windows on demand // Launch new app windows on demand
func newWindow<V: View>(withView view: @escaping () -> V, completion: @escaping () -> Void = {}) { func newWindow<V: View>(withView view: @escaping () -> V) {
findAndHideWindows(named: ["Pearcleaner"]) findAndHideWindows(named: ["Pearcleaner"])
let contentView = view let contentView = view
let frame = self.loadWindowSettings() let frame = self.loadWindowSettings()
@@ -94,26 +96,6 @@ class WindowSettings {
newWindow.contentView = NSHostingView(rootView: contentView()) newWindow.contentView = NSHostingView(rootView: contentView())
self.windows.append(newWindow) self.windows.append(newWindow)
newWindow.makeKeyAndOrderFront(nil) newWindow.makeKeyAndOrderFront(nil)
completion() resizeWindowAuto(windowSettings: self, title: "Pearcleaner")
} }
} }
//struct WillRestore: ViewModifier {
// let restore: Bool
//
// func body(content: Content) -> some View {
// content
// .onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification), perform: { output in
// let window = output.object as! NSWindow
// window.isRestorable = false
// })
// }
//}
//
//extension View {
// func willRestore(_ restoreState: Bool = true) -> some View {
// modifier(WillRestore(restore: restoreState))
// }
//}
+60 -26
View File
@@ -26,8 +26,8 @@ struct ZombieView: View {
@Binding var showPopover: Bool @Binding var showPopover: Bool
@Binding var search: String @Binding var search: String
@State private var searchZ: String = "" @State private var searchZ: String = ""
@State private var elapsedTime = 0 // @State private var elapsedTime = 0
@State private var timer: Timer? = nil // @State private var timer: Timer? = nil
@State private var selectedZombieItemsLocal: Set<URL> = [] @State private var selectedZombieItemsLocal: Set<URL> = []
@State private var memoizedFiles: [URL] = [] @State private var memoizedFiles: [URL] = []
@State private var lastSearchTermUsed: String? = nil @State private var lastSearchTermUsed: String? = nil
@@ -45,39 +45,73 @@ struct ZombieView: View {
Group { Group {
Spacer() Spacer()
Text("Searching the file system").font(.title3) HStack(spacing: 10) {
.foregroundStyle(.primary.opacity(0.5)) Text("Searching the file system").font(.title3)
.foregroundStyle(.primary.opacity(0.5))
ProgressView() ProgressView().controlSize(.small)
.progressViewStyle(.linear) }
.frame(width: 400, height: 10)
Text("\(elapsedTime)") // ProgressView()
.font(.title).monospacedDigit() // .progressViewStyle(.linear)
.foregroundStyle(.primary.opacity(0.5)) // .frame(width: 400, height: 10)
.opacity(elapsedTime == 0 ? 0 : 1)
.contentTransition(.numericText())
// Text("\(elapsedTime)")
// .font(.title).monospacedDigit()
// .foregroundStyle(.primary.opacity(0.5))
// .opacity(elapsedTime == 0 ? 0 : 1)
// .contentTransition(.numericText())
Spacer() Spacer()
} }
.transition(.opacity) .transition(.opacity)
// Spacer()
//
// HStack {
// Text("\(appState.leftoverProgress.0)").font(.title3)
// .foregroundStyle(.primary.opacity(0.5))
// Spacer()
// }
//
// HStack {
// ProgressView(value: appState.leftoverProgress.1, total: 1.0)
// .progressViewStyle(.linear)
// Button("Cancel") {
// updateOnMain {
// appState.cancelQueueOperations()
// }
// }
// .buttonStyle(SimpleButtonBrightStyle(icon: "x.circle", help: "Cancel search", color: .primary))
// }
//
//
//
// Text("\(Int(appState.leftoverProgress.1 * 100)) %")
// .font(.title).monospacedDigit()
// .foregroundStyle(.primary.opacity(0.5))
// .contentTransition(.numericText())
//
//
// Spacer()
} }
.padding(50)
.transition(.opacity)
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.onAppear { // .onAppear {
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in // self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
withAnimation { // withAnimation {
self.elapsedTime += 1 // self.elapsedTime += 1
} // }
} // }
} // }
.onDisappear { // .onDisappear {
self.timer?.invalidate() // self.timer?.invalidate()
self.timer = nil // self.timer = nil
self.elapsedTime = 0 // self.elapsedTime = 0
} // }
} else { } else {
// Titlebar // Titlebar
HStack(spacing: 0) { HStack(spacing: 0) {
@@ -226,7 +260,7 @@ struct ZombieView: View {
updateOnMain { updateOnMain {
appState.zombieFile = .empty appState.zombieFile = .empty
appState.showProgress.toggle() appState.showProgress.toggle()
reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm, reverseAddon: true) reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm)
} }
} }
.buttonStyle(RescanButton()) .buttonStyle(RescanButton())