From dbcf0ba655b6699dcb7ce2751513786fbe691042 Mon Sep 17 00:00:00 2001 From: Alin Date: Mon, 30 Sep 2024 11:20:11 -0600 Subject: [PATCH] Updates --- Pearcleaner/Logic/AppState.swift | 30 ++++--- Pearcleaner/Logic/Utilities.swift | 3 +- Pearcleaner/Logic/WindowSettings.swift | 99 +++++++++++++++++------ Pearcleaner/PearcleanerApp.swift | 38 ++++----- Pearcleaner/Settings/Interface.swift | 49 ++++++----- Pearcleaner/Settings/SettingsWindow.swift | 2 + Pearcleaner/Views/AppListItems.swift | 12 +-- Pearcleaner/Views/FilesView.swift | 6 +- Pearcleaner/Views/ZombieView.swift | 55 ------------- 9 files changed, 149 insertions(+), 145 deletions(-) diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index 663ba5f..cf8fa29 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -7,6 +7,7 @@ import Foundation import SwiftUI +import AlinFoundation //import FinderSync let home = FileManager.default.homeDirectoryForCurrentUser.path @@ -29,16 +30,27 @@ class AppState: ObservableObject { @Published var sentinelMode: Bool = false @Published var showConditionBuilder: Bool = false - var operationQueueLeftover = OperationQueue() - @Published var shouldCancelOperations = false + func getBundleSize(for appInfo: AppInfo, updateState: @escaping (Int64) -> Void) { + // Step 1: Check if the size is available and not 0 in the sortedApps cache + if let existingAppInfo = sortedApps.first(where: { $0.path == appInfo.path }), + existingAppInfo.bundleSize != 0 { + // Size is available in the cache, update the state + DispatchQueue.main.async { + updateState(existingAppInfo.bundleSize) + } + return + } - func cancelQueueOperations() { - operationQueueLeftover.cancelAllOperations() - shouldCancelOperations = true - DispatchQueue.main.async { - self.leftoverProgress = ("Search canceled", 0.0) - self.showProgress = false - self.currentView = .empty + // Step 2: If we reach here, we need to calculate the size + DispatchQueue.global(qos: .userInitiated).async { + let calculatedSize = totalSizeOnDisk(for: appInfo.path).logical + DispatchQueue.main.async { + // Update the state and the array + updateState(calculatedSize) + if let index = self.sortedApps.firstIndex(where: { $0.path == appInfo.path }) { + self.sortedApps[index].bundleSize = calculatedSize + } + } } } diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index 4c6427e..e34da71 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -50,8 +50,9 @@ func findAndSetWindowFrame(named titles: [String], windowSettings: WindowSetting for title in titles { if let window = NSApp.windows.first(where: { $0.title == title }) { window.isRestorable = false + window.isReleasedWhenClosed = false let frame = windowSettings.loadWindowSettings() - window.setFrame(frame, display: true) + window.setFrame(frame, display: true, animate: true) } } } diff --git a/Pearcleaner/Logic/WindowSettings.swift b/Pearcleaner/Logic/WindowSettings.swift index bd4487f..e02d4a3 100644 --- a/Pearcleaner/Logic/WindowSettings.swift +++ b/Pearcleaner/Logic/WindowSettings.swift @@ -9,13 +9,84 @@ import SwiftUI import AlinFoundation import Combine - -class WindowSettings { +class WindowSettings: ObservableObject { + static let shared = WindowSettings() + private let windowWidthKey = "windowWidthKey" private let windowHeightKey = "windowHeightKey" private let windowXKey = "windowXKey" private let windowYKey = "windowYKey" var windows: [NSWindow] = [] + var windowRef: NSWindow? + + init() { + trackMainWindow() + registerDefaultWindowSettings() + } + + func trackMainWindow() { + if let mainWindow = NSApplication.shared.windows.first(where: { $0.title == "Pearcleaner" }) { + windowRef = mainWindow + print("Main window detected and tracked: \(mainWindow.title)") + } + } + + // Launch new app windows on demand + func newWindow(mini: Bool, withView view: @escaping () -> V) { + let frame = self.resetWindowSettings(mini: mini) + + if windowRef == nil { + windowRef = NSWindow( + contentRect: .zero, + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, defer: false) + } + // Update the existing windowRef with all desired settings + windowRef?.contentView = NSHostingView(rootView: view()) + windowRef?.setFrame(frame, display: true, animate: true) + windowRef?.isMovableByWindowBackground = true + windowRef?.title = "Pearcleaner" + windowRef?.titlebarAppearsTransparent = true + windowRef?.isRestorable = false + windowRef?.titleVisibility = .hidden + windowRef?.makeKeyAndOrderFront(nil) + windowRef?.isReleasedWhenClosed = false +// if let curWindow = windowRef { +// print("Window exists, reopening...") +// let frame = self.resetWindowSettings(mini: mini) +// curWindow.contentView = NSHostingView(rootView: view()) +// curWindow.setFrame(frame, display: true, animate: true) +// curWindow.titlebarAppearsTransparent = true +// curWindow.isMovableByWindowBackground = true +// curWindow.title = "Pearcleaner" +// curWindow.isRestorable = false +// curWindow.titleVisibility = .hidden +// curWindow.makeKeyAndOrderFront(nil) +// curWindow.isReleasedWhenClosed = false +// return +// } +// print("Window doesn't exist, creating...") +// +// // Close existing window +// findAndHideWindows(named: ["Pearcleaner"]) +// // Create new window using defaults +// let frame = self.resetWindowSettings(mini: mini) +// let newWindow = NSWindow( +// contentRect: .zero, +// styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], +// backing: .buffered, defer: false) +// newWindow.contentView = NSHostingView(rootView: view()) +// newWindow.setFrame(frame, display: true, animate: true) +// newWindow.titlebarAppearsTransparent = true +// newWindow.isMovableByWindowBackground = true +// newWindow.title = "Pearcleaner" +// newWindow.isRestorable = false +//// self.windows.append(newWindow) +// newWindow.titleVisibility = .hidden +// newWindow.makeKeyAndOrderFront(nil) +// newWindow.isReleasedWhenClosed = false +// windowRef = newWindow + } // Register default sizes if the AppStorage keys are invalid func registerDefaultWindowSettings(completion: @escaping () -> Void = {}) { @@ -78,25 +149,7 @@ class WindowSettings { return NSRect(x: defaultX, y: defaultY, width: defaultWidth, height: defaultHeight) } - // Launch new app windows on demand - func newWindow(mini: Bool, withView view: @escaping () -> V) { - // Close existing window - findAndHideWindows(named: ["Pearcleaner"]) - // Create new window using defaults - let contentView = view - let frame = self.resetWindowSettings(mini: mini) - let newWindow = NSWindow( - contentRect: frame, - styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], - backing: .buffered, defer: false) - newWindow.contentView = NSHostingView(rootView: contentView()) - newWindow.titlebarAppearsTransparent = true - newWindow.isMovableByWindowBackground = true - newWindow.title = "Pearcleaner" - newWindow.isRestorable = false - self.windows.append(newWindow) - newWindow.titleVisibility = .hidden - newWindow.makeKeyAndOrderFront(nil) - } - } + + + diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index 3cf28b3..6c13778 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -18,7 +18,7 @@ struct PearcleanerApp: App { @StateObject private var updater = Updater(owner: "alienator88", repo: "Pearcleaner") @StateObject private var themeManager = ThemeManager.shared @StateObject private var permissionManager = PermissionManager.shared - @State private var windowSettings = WindowSettings() + @StateObject private var windowSettings = WindowSettings.shared @AppStorage("settings.permissions.hasLaunched") private var hasLaunched: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.miniview") private var miniView: Bool = true @@ -48,6 +48,8 @@ struct PearcleanerApp: App { } var body: some Scene { + + WindowGroup { Group { if mini { @@ -79,12 +81,6 @@ struct PearcleanerApp: App { } return true } - // Save window size on window dimension change -// .onChange(of: NSApplication.shared.windows.first?.frame) { newFrame in -// if let newFrame = newFrame { -// windowSettings.saveWindowSettings(frame: newFrame) -// } -// } .alert(isPresented: $appState.showUninstallAlert) { Alert( title: Text("Warning!"), @@ -108,7 +104,6 @@ struct PearcleanerApp: App { appState.currentView = .empty } - // Disable tabbing NSWindow.allowsAutomaticWindowTabbing = false @@ -127,6 +122,10 @@ struct PearcleanerApp: App { .environmentObject(permissionManager) .preferredColorScheme(themeManager.displayMode.colorScheme) }) + + findAndHideWindows(named: ["Pearcleaner"]) + NSApplication.shared.setActivationPolicy(.accessory) + } @@ -158,6 +157,7 @@ struct PearcleanerApp: App { .environmentObject(themeManager) .environmentObject(updater) .environmentObject(permissionManager) + .environmentObject(windowSettings) .preferredColorScheme(themeManager.displayMode.colorScheme) .toolbarBackground(.clear) .movableByWindowBackground() @@ -170,7 +170,7 @@ struct PearcleanerApp: App { class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { - var windowSettings = WindowSettings() + let windowSettings = WindowSettings.shared var themeManager = ThemeManager.shared var windowCloseObserver: NSObjectProtocol? var windowFrameObserver: NSObjectProtocol? @@ -182,16 +182,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { func applicationDidFinishLaunching(_ notification: Notification) { let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled") -// UserDefaults.standard.register(defaults: ["NSQuitAlwaysKeepsWindows" : false]) - findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings) + if !menubarEnabled { + findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings) + } themeManager.setupAppearance() - if menubarEnabled { - findAndHideWindows(named: ["Pearcleaner"]) - NSApplication.shared.setActivationPolicy(.accessory) - } +// if menubarEnabled { +// findAndHideWindows(named: ["Pearcleaner"]) +// NSApplication.shared.setActivationPolicy(.accessory) +// } windowFrameObserver = NotificationCenter.default.addObserver(forName: nil, object: nil, queue: nil) { notification in if let window = notification.object as? NSWindow, window.title == "Pearcleaner" { @@ -203,6 +204,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { windowCloseObserver = NotificationCenter.default.addObserver(forName: NSWindow.willCloseNotification, object: nil, queue: nil) { notification in if let window = notification.object as? NSWindow, window.title == "Pearcleaner" { + // Save window settings before removal (existing logic) self.windowSettings.saveWindowSettings(frame: window.frame) } } @@ -225,16 +227,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { - let windowSettings = WindowSettings() + let windowSettings = WindowSettings.shared if !flag { // No visible windows, so let's open a new one for window in sender.windows { window.title = "Pearcleaner" window.makeKeyAndOrderFront(self) - updateOnMain(after: 0.1, { - resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") - }) + window.setFrame(windowSettings.loadWindowSettings(), display: true, animate: true) } return true // Indicates you've handled the re-open } diff --git a/Pearcleaner/Settings/Interface.swift b/Pearcleaner/Settings/Interface.swift index 46caa51..03a1cd9 100644 --- a/Pearcleaner/Settings/Interface.swift +++ b/Pearcleaner/Settings/Interface.swift @@ -18,7 +18,7 @@ struct InterfaceSettingsTab: View { @EnvironmentObject var permissionManager: PermissionManager @EnvironmentObject var fsm: FolderSettingsManager @EnvironmentObject var themeManager: ThemeManager - @State private var windowSettings = WindowSettings() + @EnvironmentObject var windowSettings: WindowSettings @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.glass") private var glass: Bool = false @@ -287,30 +287,29 @@ struct InterfaceSettingsTab: View { } else { MenuBarExtraManager.shared.removeMenuBarExtra() NSApplication.shared.setActivationPolicy(.regular) - if !hasWindowOpen() { - if mini { - windowSettings.newWindow(mini: true, withView: { - MiniMode(search: $search, showPopover: $showPopover) - .environmentObject(appState) - .environmentObject(locations) - .environmentObject(fsm) - .environmentObject(themeManager) - .environmentObject(updater) - .environmentObject(permissionManager) - .preferredColorScheme(themeManager.displayMode.colorScheme) - }) - } else { - windowSettings.newWindow(mini: false, withView: { - RegularMode(search: $search, showPopover: $showPopover) - .environmentObject(appState) - .environmentObject(locations) - .environmentObject(fsm) - .environmentObject(themeManager) - .environmentObject(updater) - .environmentObject(permissionManager) - .preferredColorScheme(themeManager.displayMode.colorScheme) - }) - } + + if mini { + windowSettings.newWindow(mini: true, withView: { + MiniMode(search: $search, showPopover: $showPopover) + .environmentObject(appState) + .environmentObject(locations) + .environmentObject(fsm) + .environmentObject(themeManager) + .environmentObject(updater) + .environmentObject(permissionManager) + .preferredColorScheme(themeManager.displayMode.colorScheme) + }) + } else { + windowSettings.newWindow(mini: false, withView: { + RegularMode(search: $search, showPopover: $showPopover) + .environmentObject(appState) + .environmentObject(locations) + .environmentObject(fsm) + .environmentObject(themeManager) + .environmentObject(updater) + .environmentObject(permissionManager) + .preferredColorScheme(themeManager.displayMode.colorScheme) + }) } } diff --git a/Pearcleaner/Settings/SettingsWindow.swift b/Pearcleaner/Settings/SettingsWindow.swift index b74db23..a173831 100644 --- a/Pearcleaner/Settings/SettingsWindow.swift +++ b/Pearcleaner/Settings/SettingsWindow.swift @@ -13,6 +13,7 @@ struct SettingsView: View { @EnvironmentObject var fsm: FolderSettingsManager @EnvironmentObject var themeManager: ThemeManager @EnvironmentObject var updater: Updater + @EnvironmentObject var windowSettings: WindowSettings @Binding var showPopover: Bool @Binding var search: String @AppStorage("settings.general.glass") private var glass: Bool = false @@ -93,6 +94,7 @@ struct SettingsView: View { case .interface: InterfaceSettingsTab(showPopover: $showPopover, search: $search) .environmentObject(themeManager) + .environmentObject(windowSettings) case .folders: FolderSettingsTab() .environmentObject(themeManager) diff --git a/Pearcleaner/Views/AppListItems.swift b/Pearcleaner/Views/AppListItems.swift index c4c68c7..fe9c88d 100644 --- a/Pearcleaner/Views/AppListItems.swift +++ b/Pearcleaner/Views/AppListItems.swift @@ -156,16 +156,8 @@ struct AppListItems: View { } .onAppear { if self.bundleSize == 0 { - DispatchQueue.global(qos: .userInitiated).async { - let size = totalSizeOnDisk(for: appInfo.path).logical - DispatchQueue.main.async { - print("Sizing up") - self.bundleSize = size - // Update the appInfo in the appState array - if let index = appState.sortedApps.firstIndex(where: { $0.path == appInfo.path }) { - appState.sortedApps[index].bundleSize = size - } - } + appState.getBundleSize(for: appInfo) { size in + self.bundleSize = size } } } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index c417017..f4d6ed2 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -407,7 +407,7 @@ struct FilesView: View { .font(.headline) Divider() Spacer() - Text("Always double-check the files/folders marked for removal. In some rare cases, Pearcleaner may find some unrelated files when app names are too similar.") + Text("Always confirm the files marked for removal. In rare cases, unrelated files may be found when app names are too similar.\n\nNOTE: Pearcleaner uses AppleScript to remove files. Currently macOS does not allow AppleScript to authenticate using the fingerprint sensor, only password authentication is supported.") .font(.subheadline) Spacer() Button("Close") { @@ -415,10 +415,10 @@ struct FilesView: View { showAlert = false } .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", label: "Close", help: "Dismiss")) - Spacer() +// Spacer() } .padding(15) - .frame(width: 400, height: 220) + .frame(width: 400, height: 250) .background(GlassEffect(material: .hudWindow, blendingMode: .behindWindow)) }) .sheet(isPresented: $appState.showConditionBuilder, content: { diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index 758cb19..2c3ae19 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -28,8 +28,6 @@ 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 selectedZombieItemsLocal: Set = [] @State private var memoizedFiles: [URL] = [] @State private var lastSearchTermUsed: String? = nil @@ -53,67 +51,14 @@ struct ZombieView: View { ProgressView().controlSize(.small) } - -// 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() } .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 -// } } else { // Titlebar HStack(spacing: 0) {