diff --git a/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fbe7ffa..4299a74 100644 --- a/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Pearcleaner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "284df40ab461728f91e9fff8f6afbaf9f1121ce265b549a5187092a1097d4db1", + "originHash" : "bebe4a176f4f3b9e625a60cb3048363d2aeb03aa06dabf97821c3b73b1c5f149", "pins" : [ { "identity" : "alinfoundation", @@ -7,7 +7,7 @@ "location" : "https://github.com/alienator88/AlinFoundation", "state" : { "branch" : "main", - "revision" : "d660e9a1fb07ec3fa036c89c6f9fc1d573cecf9e" + "revision" : "986ac354d2a01f87ed4c37d19ce80a752a62fd4c" } }, { diff --git a/Pearcleaner/Logic/AppPathsFetch.swift b/Pearcleaner/Logic/AppPathsFetch.swift index 3ea6a6a..5ab74cf 100644 --- a/Pearcleaner/Logic/AppPathsFetch.swift +++ b/Pearcleaner/Logic/AppPathsFetch.swift @@ -15,18 +15,18 @@ class AppPathFinder { private var appState: AppState private var locations: Locations private var backgroundRun: Bool - private var reverseAddon: Bool +// private var reverseAddon: Bool private var undo: Bool private var completion: () -> Void = {} private var collection: [URL] = [] 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.appState = appState self.locations = locations self.backgroundRun = backgroundRun - self.reverseAddon = reverseAddon +// self.reverseAddon = reverseAddon self.undo = undo self.completion = completion } @@ -338,9 +338,9 @@ class AppPathFinder { } // Append object to store if running reverse search with empty store - if self.reverseAddon { - self.appState.appInfoStore.append(self.appInfo) - } +// if self.reverseAddon { +// self.appState.appInfoStore.append(self.appInfo) +// } self.completion() } diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index cbaf64b..84c7554 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -13,7 +13,7 @@ let home = FileManager.default.homeDirectoryForCurrentUser.path class AppState: ObservableObject { @Published var appInfo: AppInfo - @Published var appInfoStore: [AppInfo] = [] +// @Published var appInfoStore: [AppInfo] = [] @Published var trashedFiles: [AppInfo] = [] @Published var zombieFile: ZombieFile @Published var sortedApps: [AppInfo] = [] @@ -23,12 +23,24 @@ class AppState: ObservableObject { @Published var sidebar: Bool = true @Published var reload: Bool = false @Published var showProgress: Bool = false + @Published var leftoverProgress: (String, Double) = ("", 0.0) @Published var finderExtensionEnabled: Bool = false @Published var showUninstallAlert: Bool = false @Published var oneShotMode: 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() { self.appInfo = AppInfo( diff --git a/Pearcleaner/Logic/Conditions.swift b/Pearcleaner/Logic/Conditions.swift index 71f4e66..5e00810 100644 --- a/Pearcleaner/Logic/Conditions.swift +++ b/Pearcleaner/Logic/Conditions.swift @@ -52,53 +52,62 @@ var conditions: [Condition] = [ Condition( bundle_id: "com.robotsandpencils.xcodesapp", 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( bundle_id: "io.hyperapp.xcodecleaner", 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( bundle_id: "us.zoom.xos", include: ["zoom"], - exclude: [] + exclude: [], + includeForce: nil ), Condition( bundle_id: "com.brave.browser", include: ["brave"], - exclude: [] + exclude: [], + includeForce: nil ), Condition( bundle_id: "com.okta.mobile", include: ["okta"], - exclude: [] + exclude: [], + includeForce: nil ), Condition( bundle_id: "com.google.chrome", include: ["google", "chrome"], - exclude: ["iterm", "chromefeaturestate"] + exclude: ["iterm", "chromefeaturestate"], + includeForce: nil ), Condition( bundle_id: "com.microsoft.edgemac", include: ["microsoft"], - exclude: ["vscode", "rdc", "appcenter", "office", "oneauth"] + exclude: ["vscode", "rdc", "appcenter", "office", "oneauth"], + includeForce: nil ), Condition( bundle_id: "org.mozilla.firefox", include: ["mozilla", "firefox"], - exclude: [] + exclude: [], + includeForce: nil ), Condition( bundle_id: "org.mozilla.firefox.nightly", include: ["mozilla", "firefox"], - exclude: [] + exclude: [], + includeForce: nil ), Condition( bundle_id: "com.logi.optionsplus", include: ["logi"], exclude: ["login", "logic"], - includeForce: [] + includeForce: nil ), Condition( bundle_id: "com.microsoft.vscode", @@ -109,17 +118,20 @@ var conditions: [Condition] = [ Condition( bundle_id: "com.facebook.archon.developerid", include: ["archon.loginhelper"], - exclude: [] + exclude: [], + includeForce: nil ), Condition( bundle_id: "eu.exelban.stats", include: [], - exclude: ["video"] + exclude: ["video"], + includeForce: nil ), Condition( bundle_id: "jetbrains", include: ["jetbrains", "jcef"], - exclude: [] + exclude: [], + includeForce: nil ), ] diff --git a/Pearcleaner/Logic/Logic.swift b/Pearcleaner/Logic/Logic.swift index ee85eeb..6bd62ca 100644 --- a/Pearcleaner/Logic/Logic.swift +++ b/Pearcleaner/Logic/Logic.swift @@ -109,45 +109,108 @@ func listAppSupportDirectories() -> [String] { // Load app paths on launch -func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locations, fsm: FolderSettingsManager, reverseAddon: Bool = false, completion: @escaping () -> Void = {}) { - let dispatchGroup = DispatchGroup() - appState.appInfoStore.removeAll() - - for app in allApps { - dispatchGroup.enter() - DispatchQueue.global(qos: .background).async { - AppPathFinder(appInfo: app, appState: appState, locations: locations, backgroundRun: true, reverseAddon: reverseAddon).findPaths() - dispatchGroup.leave() - } - } +func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locations, fsm: FolderSettingsManager, completion: @escaping () -> Void = {}) { +// appState.operationQueueLeftover.maxConcurrentOperationCount = 10 // Adjust this value as needed +// appState.shouldCancelOperations = false +// appState.appInfoStore.removeAll() +// let sortedAllApps = allApps.sorted { $0.appName.localizedCompare($1.appName) == .orderedAscending } - dispatchGroup.notify(queue: DispatchQueue.global(qos: .background)) { - - func checkAllAppsProcessed(retryCount: Int = 0, maxRetry: Int = 120) { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - if appState.appInfoStore.count == allApps.count { - 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() - } + updateOnMain { + appState.leftoverProgress.0 = "Finding leftover files, please wait..." + } + ReversePathsSearcher(appState: appState, locations: locations, fsm: fsm, sortedApps: allApps).reversePathsSearch { + updateOnMain { + printOS("Reverse search processed successfully") + appState.showProgress = false + withAnimation { + appState.leftoverProgress.1 = 0.0 } + appState.leftoverProgress.0 = "Reverse search completed successfully" } - - checkAllAppsProcessed() - + completion() } + +// 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.selectedItems = [] - // 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() + // 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 } - } 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() + }.findPaths() - appState.appInfo = appInfo + appState.appInfo = appInfo - // Animate the view change and popover display. - withAnimation(Animation.easeIn(duration: 0.4)) { - appState.currentView = .files - showPopover.wrappedValue.toggle() - } + // Animate the view change and popover display. + withAnimation(Animation.easeIn(duration: 0.4)) { + 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() +// } +// } } } diff --git a/Pearcleaner/Logic/ReversePathsFetch.swift b/Pearcleaner/Logic/ReversePathsFetch.swift index 45f6491..0ce4ac5 100644 --- a/Pearcleaner/Logic/ReversePathsFetch.swift +++ b/Pearcleaner/Logic/ReversePathsFetch.swift @@ -19,15 +19,15 @@ class ReversePathsSearcher { private var fileSizeLogical: [URL: Int64] = [:] private var fileIcon: [URL: NSImage?] = [:] 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.locations = locations self.fsm = fsm + self.sortedApps = sortedApps } - - func reversePathsSearch(completion: @escaping () -> Void = {}) { Task(priority: .high) { self.processLocations() @@ -38,51 +38,74 @@ class ReversePathsSearcher { } 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) { dispatchGroup.enter() - processLocation(location, allPaths: allPaths, allNames: allNames) + processLocation(location) dispatchGroup.leave() } } - private func processLocation(_ location: String, allPaths: [String], allNames: [String]) { + private func processLocation(_ location: String) { do { let contents = try fileManager.contentsOfDirectory(atPath: location) contents.forEach { itemName in let itemURL = URL(fileURLWithPath: location).appendingPathComponent(itemName) - processItem(itemName, itemURL: itemURL, allPaths: allPaths, allNames: allNames) + processItem(itemName, itemURL: itemURL) } } catch { printOS("Error processing location: \(location), error: \(error)") } } - private func processItem(_ itemName: String, itemURL: URL, allPaths: [String], allNames: [String]) { - let formattedItemName = itemName.pearFormat() + private func processItem(_ itemName: String, itemURL: URL) { let itemPath = itemURL.path.pearFormat() - let itemLastPathComponent = itemURL.lastPathComponent.pearFormat() let exclusionList = fsm.fileFolderPathsZ.map { $0.pearFormat() } if exclusionList.contains(itemPath) || itemPath.contains("dsstore") || itemPath.contains("daemonnameoridentifierhere") { return } - guard !skipReverse.contains(where: { formattedItemName.contains($0) }), - !allPaths.contains(where: { $0 == itemPath || $0.hasSuffix("/\(itemLastPathComponent)") }), - !allNames.contains(formattedItemName), - isSupportedFileType(at: itemURL.path) else { + guard !skipReverse.contains(where: { itemName.pearFormat().contains($0) }), + isSupportedFileType(at: itemURL.path), + !isRelatedToInstalledApp(itemPath: itemPath), + !isExcludedByConditions(itemPath: itemPath) else { + return } 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() { collection.forEach { path in let size = totalSizeOnDisk(for: path) @@ -104,3 +127,5 @@ class ReversePathsSearcher { } } + + diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index a95961a..02d8a14 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -208,9 +208,9 @@ func removeApp(appState: AppState, withPath path: URL) { // return // Exit the function if the app was found and removed } // Remove from appInfoStore if found - if let index = appState.appInfoStore.firstIndex(where: { $0.path == path }) { - appState.appInfoStore.remove(at: index) - } +// if let index = appState.appInfoStore.firstIndex(where: { $0.path == path }) { +// appState.appInfoStore.remove(at: index) +// } // Brew cleanup if enabled if brew { caskCleanup(app: appState.appInfo.appName) diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index 771f625..cabec23 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -25,7 +25,6 @@ struct PearcleanerApp: App { @AppStorage("settings.general.brew") private var brew: Bool = false @AppStorage("settings.menubar.enabled") private var menubarEnabled: 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 showPopover: Bool = false let conditionManager = ConditionManager.shared @@ -34,11 +33,11 @@ struct PearcleanerApp: App { var body: some Scene { WindowGroup { Group { - if !mini { - RegularMode(search: $search, showPopover: $showPopover) - } else { - MiniMode(search: $search, showPopover: $showPopover) - } + if mini { + MiniMode(search: $search, showPopover: $showPopover) + } else { + RegularMode(search: $search, showPopover: $showPopover) + } } .environmentObject(appState) .environmentObject(locations) @@ -92,13 +91,13 @@ struct PearcleanerApp: App { appState.currentView = .empty } + // Disable tabbing NSWindow.allowsAutomaticWindowTabbing = false // Load apps list on startup reloadAppsList(appState: appState, fsm: fsm) - // Enable menubar item if menubarEnabled { MenuBarExtraManager.shared.addMenuBarExtra(withView: { @@ -110,19 +109,13 @@ struct PearcleanerApp: App { .environmentObject(updater) .environmentObject(permissionManager) .preferredColorScheme(themeManager.displayMode.colorScheme) - }, icon: selectedMenubarIcon) + }) } #if !DEBUG 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 // ensureApplicationSupportFolderExists(appState: appState) @@ -135,9 +128,7 @@ struct PearcleanerApp: App { .windowStyle(.hiddenTitleBar) .windowResizability(.contentMinSize) .commands { - AppCommands(appState: appState, locations: locations, fsm: fsm, updater: updater, themeManager: themeManager) -// CommandGroup(replacing: .newItem, addition: { }) - + AppCommands(appState: appState, locations: locations, fsm: fsm, updater: updater, themeManager: themeManager) } @@ -174,7 +165,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { // UserDefaults.standard.register(defaults: ["NSQuitAlwaysKeepsWindows" : false]) findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings) - + themeManager.setupAppearance() if menubarEnabled { diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index 480975f..fa3719e 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -14,7 +14,6 @@ struct GeneralSettingsTab: View { @EnvironmentObject var appState: AppState @EnvironmentObject var locations: Locations @EnvironmentObject var themeManager: ThemeManager - @State private var windowSettings = WindowSettings() @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false @AppStorage("settings.general.brew") private var brew: Bool = false @AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false diff --git a/Pearcleaner/Settings/Interface.swift b/Pearcleaner/Settings/Interface.swift index e02d1e1..22bf1fa 100644 --- a/Pearcleaner/Settings/Interface.swift +++ b/Pearcleaner/Settings/Interface.swift @@ -24,7 +24,7 @@ struct InterfaceSettingsTab: View { @AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.popover") private var popoverStay: 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 @Binding var showPopover: Bool @Binding var search: String @@ -143,9 +143,7 @@ struct InterfaceSettingsTab: View { .environmentObject(updater) .environmentObject(permissionManager) .preferredColorScheme(themeManager.displayMode.colorScheme) - }) { - resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") - } + }) } else { if appState.appInfo.appName.isEmpty { appState.currentView = .empty @@ -162,9 +160,7 @@ struct InterfaceSettingsTab: View { .environmentObject(permissionManager) .preferredColorScheme(themeManager.displayMode.colorScheme) } - ) { - resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") - } + ) } @@ -265,10 +261,9 @@ struct InterfaceSettingsTab: View { .environmentObject(updater) .environmentObject(permissionManager) .preferredColorScheme(themeManager.displayMode.colorScheme) - }, icon: selectedMenubarIcon) + }) NSApplication.shared.setActivationPolicy(.accessory) findAndHideWindows(named: ["Pearcleaner"]) - // findAndShowWindows(named: ["Pearcleaner", "Interface"]) } else { MenuBarExtraManager.shared.removeMenuBarExtra() NSApplication.shared.setActivationPolicy(.regular) @@ -283,9 +278,7 @@ struct InterfaceSettingsTab: View { .environmentObject(updater) .environmentObject(permissionManager) .preferredColorScheme(themeManager.displayMode.colorScheme) - }) { - resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") - } + }) } else { windowSettings.newWindow(withView: { RegularMode(search: $search, showPopover: $showPopover) @@ -296,9 +289,7 @@ struct InterfaceSettingsTab: View { .environmentObject(updater) .environmentObject(permissionManager) .preferredColorScheme(themeManager.displayMode.colorScheme) - }) { - resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") - } + }) } } diff --git a/Pearcleaner/Views/AppListItems.swift b/Pearcleaner/Views/AppListItems.swift index 26d9fa6..6bea604 100644 --- a/Pearcleaner/Views/AppListItems.swift +++ b/Pearcleaner/Views/AppListItems.swift @@ -14,7 +14,6 @@ struct AppListItems: View { @EnvironmentObject var themeManager: ThemeManager @Binding var search: String @State private var isHovered = false - @State private var windowSettings = WindowSettings() @Environment(\.colorScheme) var colorScheme @AppStorage("displayMode") var displayMode: DisplayMode = .system @AppStorage("settings.general.miniview") private var miniView: Bool = true @@ -79,13 +78,10 @@ struct AppListItems: View { ) } - if bundleSize == 0 { - ProgressView().controlSize(.mini).padding(.leading, 5) - } else { - Text("\(isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human)") - .font(.system(size: (isHovered || isSelected) ? 12 : 10)) - .foregroundStyle(.primary.opacity(0.5)) - } + Text(bundleSize == 0 ? "v\(appInfo.appVersion)" : (isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human)) + .font(.system(size: (isHovered || isSelected) ? 12 : 10)) + .foregroundStyle(.primary.opacity(0.5)) + } } diff --git a/Pearcleaner/Views/AppSearchView.swift b/Pearcleaner/Views/AppSearchView.swift index bfa1453..6b672dc 100644 --- a/Pearcleaner/Views/AppSearchView.swift +++ b/Pearcleaner/Views/AppSearchView.swift @@ -105,7 +105,7 @@ struct AppSearchView: View { appState.currentView = .zombie appState.showProgress.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 { appState.currentView = .zombie showPopover.toggle() diff --git a/Pearcleaner/Views/AppsListView.swift b/Pearcleaner/Views/AppsListView.swift index 28e593c..71ee5d8 100644 --- a/Pearcleaner/Views/AppsListView.swift +++ b/Pearcleaner/Views/AppsListView.swift @@ -46,13 +46,11 @@ struct SectionView: View { @Binding var showPopover: Bool var body: some View { - VStack(spacing: 0) { + LazyVStack(spacing: 0) { Header(title: title, count: count, showPopover: $showPopover) .padding(.leading, 5) ForEach(apps, id: \.self) { appInfo in AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) - if appInfo != apps.last { - } } } } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index d58f9dc..e14d69e 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -24,9 +24,8 @@ struct FilesView: View { @Environment(\.colorScheme) var colorScheme @Binding var showPopover: Bool @Binding var search: String - @State private var elapsedTime = 0 - @State private var timer: Timer? = nil - @State private var isShowingInspector = false +// @State private var elapsedTime = 0 +// @State private var timer: Timer? = nil var body: some View { @@ -51,35 +50,42 @@ struct FilesView: View { if appState.showProgress { VStack { Spacer() - Text("Searching the file system").font(.title3) - .foregroundStyle((.primary.opacity(0.5))) - ProgressView() - .progressViewStyle(.linear) - .frame(width: 400, height: 10) + HStack(spacing: 10) { + Text("Searching the file system").font(.title3) + .foregroundStyle(.primary.opacity(0.5)) + ProgressView().controlSize(.small) + } - Text("\(elapsedTime)") - .font(.title).monospacedDigit() - .foregroundStyle((.primary.opacity(0.5))) - .opacity(elapsedTime == 0 ? 0 : 1) - .contentTransition(.numericText()) +// Text("Searching the file system").font(.title3) +// .foregroundStyle((.primary.opacity(0.5))) +// +// ProgressView() +// .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() } .frame(maxWidth: .infinity, maxHeight: .infinity) .transition(.opacity) - .onAppear { - self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in - withAnimation { - self.elapsedTime += 1 - } - } - } - .onDisappear { - self.timer?.invalidate() - self.timer = nil - self.elapsedTime = 0 - } +// .onAppear { +// self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in +// withAnimation { +// self.elapsedTime += 1 +// } +// } +// } +// .onDisappear { +// self.timer?.invalidate() +// self.timer = nil +// self.elapsedTime = 0 +// } } else { // Titlebar HStack(spacing: 0) { @@ -355,9 +361,9 @@ struct FilesView: View { appState.trashedFiles.append(appState.appInfo) // Clear out appInfoStore object (Used for leftover file search) - if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) { - appState.appInfoStore[index] = .empty - } +// if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) { +// appState.appInfoStore[index] = .empty +// } updateOnMain { // Remove items from the list diff --git a/Pearcleaner/Views/MenuBarItem.swift b/Pearcleaner/Views/MenuBarItem.swift index 6682a56..f81d88a 100644 --- a/Pearcleaner/Views/MenuBarItem.swift +++ b/Pearcleaner/Views/MenuBarItem.swift @@ -13,21 +13,20 @@ class MenuBarExtraManager { private var statusItem: NSStatusItem? private var popover = NSPopover() private var lastView: (() -> AnyView)? - private var lastIcon: String? + @AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "bubbles.and.sparkles.fill" - func addMenuBarExtra(withView view: @escaping () -> V, icon: String) { + func addMenuBarExtra(withView view: @escaping () -> V) { guard statusItem == nil else { return } - // Remember the last view and icon + // Remember the last view lastView = { AnyView(view()) } - lastIcon = icon // Initialize the status item statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) // Set up the status item's 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.target = self button.sendAction(on: [.leftMouseDown, .rightMouseDown]) @@ -51,8 +50,6 @@ class MenuBarExtraManager { guard let button = statusItem?.button else { return } if let image = NSImage(systemSymbolName: icon, accessibilityDescription: nil) { button.image = image - } else { - button.image = NSImage(named: icon) } } @@ -61,8 +58,8 @@ class MenuBarExtraManager { self.removeMenuBarExtra() // Ensure the last view and icon are available before re-adding - if let lastView = self.lastView, let lastIcon = self.lastIcon { - self.addMenuBarExtra(withView: lastView, icon: lastIcon) + if let lastView = self.lastView { + self.addMenuBarExtra(withView: lastView) } } } diff --git a/Pearcleaner/Views/WindowSettings.swift b/Pearcleaner/Views/WindowSettings.swift index 0aa3e1d..17806ec 100644 --- a/Pearcleaner/Views/WindowSettings.swift +++ b/Pearcleaner/Views/WindowSettings.swift @@ -7,6 +7,8 @@ import SwiftUI import AlinFoundation +import Combine + class WindowSettings { private let windowWidthKey = "windowWidthKey" @@ -76,7 +78,7 @@ class WindowSettings { } // Launch new app windows on demand - func newWindow(withView view: @escaping () -> V, completion: @escaping () -> Void = {}) { + func newWindow(withView view: @escaping () -> V) { findAndHideWindows(named: ["Pearcleaner"]) let contentView = view let frame = self.loadWindowSettings() @@ -94,26 +96,6 @@ class WindowSettings { newWindow.contentView = NSHostingView(rootView: contentView()) self.windows.append(newWindow) 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)) -// } -//} diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index 3ff5f5c..844523b 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -26,8 +26,8 @@ struct ZombieView: View { @Binding var showPopover: Bool @Binding var search: String @State private var searchZ: String = "" - @State private var elapsedTime = 0 - @State private var timer: Timer? = nil +// @State private var elapsedTime = 0 +// @State private var timer: Timer? = nil @State private var selectedZombieItemsLocal: Set = [] @State private var memoizedFiles: [URL] = [] @State private var lastSearchTermUsed: String? = nil @@ -45,39 +45,73 @@ struct ZombieView: View { Group { Spacer() - Text("Searching the file system").font(.title3) - .foregroundStyle(.primary.opacity(0.5)) + HStack(spacing: 10) { + Text("Searching the file system").font(.title3) + .foregroundStyle(.primary.opacity(0.5)) + ProgressView().controlSize(.small) + } + - ProgressView() - .progressViewStyle(.linear) - .frame(width: 400, height: 10) +// ProgressView() +// .progressViewStyle(.linear) +// .frame(width: 400, height: 10) - Text("\(elapsedTime)") - .font(.title).monospacedDigit() - .foregroundStyle(.primary.opacity(0.5)) - .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() } .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) - .onAppear { - self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in - withAnimation { - self.elapsedTime += 1 - } - } - } - .onDisappear { - self.timer?.invalidate() - self.timer = nil - self.elapsedTime = 0 - } +// .onAppear { +// self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in +// withAnimation { +// self.elapsedTime += 1 +// } +// } +// } +// .onDisappear { +// self.timer?.invalidate() +// self.timer = nil +// self.elapsedTime = 0 +// } } else { // Titlebar HStack(spacing: 0) { @@ -226,7 +260,7 @@ struct ZombieView: View { updateOnMain { appState.zombieFile = .empty 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())