This commit is contained in:
Alin
2024-09-30 11:20:11 -06:00
parent d8f650116c
commit dbcf0ba655
9 changed files with 149 additions and 145 deletions
+21 -9
View File
@@ -7,6 +7,7 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
import AlinFoundation
//import FinderSync //import FinderSync
let home = FileManager.default.homeDirectoryForCurrentUser.path let home = FileManager.default.homeDirectoryForCurrentUser.path
@@ -29,16 +30,27 @@ class AppState: ObservableObject {
@Published var sentinelMode: Bool = false @Published var sentinelMode: Bool = false
@Published var showConditionBuilder: Bool = false @Published var showConditionBuilder: Bool = false
var operationQueueLeftover = OperationQueue() func getBundleSize(for appInfo: AppInfo, updateState: @escaping (Int64) -> Void) {
@Published var shouldCancelOperations = false // 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 }),
func cancelQueueOperations() { existingAppInfo.bundleSize != 0 {
operationQueueLeftover.cancelAllOperations() // Size is available in the cache, update the state
shouldCancelOperations = true
DispatchQueue.main.async { DispatchQueue.main.async {
self.leftoverProgress = ("Search canceled", 0.0) updateState(existingAppInfo.bundleSize)
self.showProgress = false }
self.currentView = .empty return
}
// 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
}
}
} }
} }
+2 -1
View File
@@ -50,8 +50,9 @@ func findAndSetWindowFrame(named titles: [String], windowSettings: WindowSetting
for title in titles { for title in titles {
if let window = NSApp.windows.first(where: { $0.title == title }) { if let window = NSApp.windows.first(where: { $0.title == title }) {
window.isRestorable = false window.isRestorable = false
window.isReleasedWhenClosed = false
let frame = windowSettings.loadWindowSettings() let frame = windowSettings.loadWindowSettings()
window.setFrame(frame, display: true) window.setFrame(frame, display: true, animate: true)
} }
} }
} }
+74 -21
View File
@@ -9,13 +9,84 @@ import SwiftUI
import AlinFoundation import AlinFoundation
import Combine import Combine
class WindowSettings: ObservableObject {
static let shared = WindowSettings()
class WindowSettings {
private let windowWidthKey = "windowWidthKey" private let windowWidthKey = "windowWidthKey"
private let windowHeightKey = "windowHeightKey" private let windowHeightKey = "windowHeightKey"
private let windowXKey = "windowXKey" private let windowXKey = "windowXKey"
private let windowYKey = "windowYKey" private let windowYKey = "windowYKey"
var windows: [NSWindow] = [] 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<V: View>(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 // Register default sizes if the AppStorage keys are invalid
func registerDefaultWindowSettings(completion: @escaping () -> Void = {}) { func registerDefaultWindowSettings(completion: @escaping () -> Void = {}) {
@@ -78,25 +149,7 @@ class WindowSettings {
return NSRect(x: defaultX, y: defaultY, width: defaultWidth, height: defaultHeight) return NSRect(x: defaultX, y: defaultY, width: defaultWidth, height: defaultHeight)
} }
// Launch new app windows on demand
func newWindow<V: View>(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)
} }
}
+18 -18
View File
@@ -18,7 +18,7 @@ struct PearcleanerApp: App {
@StateObject private var updater = Updater(owner: "alienator88", repo: "Pearcleaner") @StateObject private var updater = Updater(owner: "alienator88", repo: "Pearcleaner")
@StateObject private var themeManager = ThemeManager.shared @StateObject private var themeManager = ThemeManager.shared
@StateObject private var permissionManager = PermissionManager.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.permissions.hasLaunched") private var hasLaunched: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false
@AppStorage("settings.general.miniview") private var miniView: Bool = true @AppStorage("settings.general.miniview") private var miniView: Bool = true
@@ -48,6 +48,8 @@ struct PearcleanerApp: App {
} }
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
Group { Group {
if mini { if mini {
@@ -79,12 +81,6 @@ struct PearcleanerApp: App {
} }
return true 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(isPresented: $appState.showUninstallAlert) {
Alert( Alert(
title: Text("Warning!"), title: Text("Warning!"),
@@ -108,7 +104,6 @@ struct PearcleanerApp: App {
appState.currentView = .empty appState.currentView = .empty
} }
// Disable tabbing // Disable tabbing
NSWindow.allowsAutomaticWindowTabbing = false NSWindow.allowsAutomaticWindowTabbing = false
@@ -127,6 +122,10 @@ struct PearcleanerApp: App {
.environmentObject(permissionManager) .environmentObject(permissionManager)
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
}) })
findAndHideWindows(named: ["Pearcleaner"])
NSApplication.shared.setActivationPolicy(.accessory)
} }
@@ -158,6 +157,7 @@ struct PearcleanerApp: App {
.environmentObject(themeManager) .environmentObject(themeManager)
.environmentObject(updater) .environmentObject(updater)
.environmentObject(permissionManager) .environmentObject(permissionManager)
.environmentObject(windowSettings)
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
.toolbarBackground(.clear) .toolbarBackground(.clear)
.movableByWindowBackground() .movableByWindowBackground()
@@ -170,7 +170,7 @@ struct PearcleanerApp: App {
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
var windowSettings = WindowSettings() let windowSettings = WindowSettings.shared
var themeManager = ThemeManager.shared var themeManager = ThemeManager.shared
var windowCloseObserver: NSObjectProtocol? var windowCloseObserver: NSObjectProtocol?
var windowFrameObserver: NSObjectProtocol? var windowFrameObserver: NSObjectProtocol?
@@ -182,16 +182,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
func applicationDidFinishLaunching(_ notification: Notification) { func applicationDidFinishLaunching(_ notification: Notification) {
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled") let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
// UserDefaults.standard.register(defaults: ["NSQuitAlwaysKeepsWindows" : false])
if !menubarEnabled {
findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings) findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings)
}
themeManager.setupAppearance() themeManager.setupAppearance()
if menubarEnabled { // if menubarEnabled {
findAndHideWindows(named: ["Pearcleaner"]) // findAndHideWindows(named: ["Pearcleaner"])
NSApplication.shared.setActivationPolicy(.accessory) // NSApplication.shared.setActivationPolicy(.accessory)
} // }
windowFrameObserver = NotificationCenter.default.addObserver(forName: nil, object: nil, queue: nil) { notification in windowFrameObserver = NotificationCenter.default.addObserver(forName: nil, object: nil, queue: nil) { notification in
if let window = notification.object as? NSWindow, window.title == "Pearcleaner" { 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 windowCloseObserver = NotificationCenter.default.addObserver(forName: NSWindow.willCloseNotification, object: nil, queue: nil) { notification in
if let window = notification.object as? NSWindow, window.title == "Pearcleaner" { if let window = notification.object as? NSWindow, window.title == "Pearcleaner" {
// Save window settings before removal (existing logic)
self.windowSettings.saveWindowSettings(frame: window.frame) self.windowSettings.saveWindowSettings(frame: window.frame)
} }
} }
@@ -225,16 +227,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
let windowSettings = WindowSettings() let windowSettings = WindowSettings.shared
if !flag { if !flag {
// No visible windows, so let's open a new one // No visible windows, so let's open a new one
for window in sender.windows { for window in sender.windows {
window.title = "Pearcleaner" window.title = "Pearcleaner"
window.makeKeyAndOrderFront(self) window.makeKeyAndOrderFront(self)
updateOnMain(after: 0.1, { window.setFrame(windowSettings.loadWindowSettings(), display: true, animate: true)
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
})
} }
return true // Indicates you've handled the re-open return true // Indicates you've handled the re-open
} }
+2 -3
View File
@@ -18,7 +18,7 @@ struct InterfaceSettingsTab: View {
@EnvironmentObject var permissionManager: PermissionManager @EnvironmentObject var permissionManager: PermissionManager
@EnvironmentObject var fsm: FolderSettingsManager @EnvironmentObject var fsm: FolderSettingsManager
@EnvironmentObject var themeManager: ThemeManager @EnvironmentObject var themeManager: ThemeManager
@State private var windowSettings = WindowSettings() @EnvironmentObject var windowSettings: WindowSettings
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false
@AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.glass") private var glass: Bool = false
@@ -287,7 +287,7 @@ struct InterfaceSettingsTab: View {
} else { } else {
MenuBarExtraManager.shared.removeMenuBarExtra() MenuBarExtraManager.shared.removeMenuBarExtra()
NSApplication.shared.setActivationPolicy(.regular) NSApplication.shared.setActivationPolicy(.regular)
if !hasWindowOpen() {
if mini { if mini {
windowSettings.newWindow(mini: true, withView: { windowSettings.newWindow(mini: true, withView: {
MiniMode(search: $search, showPopover: $showPopover) MiniMode(search: $search, showPopover: $showPopover)
@@ -311,7 +311,6 @@ struct InterfaceSettingsTab: View {
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
}) })
} }
}
} }
} }
@@ -13,6 +13,7 @@ struct SettingsView: View {
@EnvironmentObject var fsm: FolderSettingsManager @EnvironmentObject var fsm: FolderSettingsManager
@EnvironmentObject var themeManager: ThemeManager @EnvironmentObject var themeManager: ThemeManager
@EnvironmentObject var updater: Updater @EnvironmentObject var updater: Updater
@EnvironmentObject var windowSettings: WindowSettings
@Binding var showPopover: Bool @Binding var showPopover: Bool
@Binding var search: String @Binding var search: String
@AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.glass") private var glass: Bool = false
@@ -93,6 +94,7 @@ struct SettingsView: View {
case .interface: case .interface:
InterfaceSettingsTab(showPopover: $showPopover, search: $search) InterfaceSettingsTab(showPopover: $showPopover, search: $search)
.environmentObject(themeManager) .environmentObject(themeManager)
.environmentObject(windowSettings)
case .folders: case .folders:
FolderSettingsTab() FolderSettingsTab()
.environmentObject(themeManager) .environmentObject(themeManager)
+1 -9
View File
@@ -156,16 +156,8 @@ struct AppListItems: View {
} }
.onAppear { .onAppear {
if self.bundleSize == 0 { if self.bundleSize == 0 {
DispatchQueue.global(qos: .userInitiated).async { appState.getBundleSize(for: appInfo) { size in
let size = totalSizeOnDisk(for: appInfo.path).logical
DispatchQueue.main.async {
print("Sizing up")
self.bundleSize = size 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
}
}
} }
} }
} }
+3 -3
View File
@@ -407,7 +407,7 @@ struct FilesView: View {
.font(.headline) .font(.headline)
Divider() Divider()
Spacer() 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) .font(.subheadline)
Spacer() Spacer()
Button("Close") { Button("Close") {
@@ -415,10 +415,10 @@ struct FilesView: View {
showAlert = false showAlert = false
} }
.buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", label: "Close", help: "Dismiss")) .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", label: "Close", help: "Dismiss"))
Spacer() // Spacer()
} }
.padding(15) .padding(15)
.frame(width: 400, height: 220) .frame(width: 400, height: 250)
.background(GlassEffect(material: .hudWindow, blendingMode: .behindWindow)) .background(GlassEffect(material: .hudWindow, blendingMode: .behindWindow))
}) })
.sheet(isPresented: $appState.showConditionBuilder, content: { .sheet(isPresented: $appState.showConditionBuilder, content: {
-55
View File
@@ -28,8 +28,6 @@ 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 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
@@ -53,67 +51,14 @@ struct ZombieView: View {
ProgressView().controlSize(.small) 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() 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) .padding(50)
.transition(.opacity) .transition(.opacity)
.frame(maxWidth: .infinity, maxHeight: .infinity) .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 { } else {
// Titlebar // Titlebar
HStack(spacing: 0) { HStack(spacing: 0) {