mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 03:48:55 +00:00
Updates
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,84 @@ import SwiftUI
|
||||
import AlinFoundation
|
||||
import Combine
|
||||
|
||||
class WindowSettings: ObservableObject {
|
||||
static let shared = WindowSettings()
|
||||
|
||||
class 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<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
|
||||
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<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,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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<URL> = []
|
||||
@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) {
|
||||
|
||||
Reference in New Issue
Block a user