This commit is contained in:
Alin
2024-03-16 18:52:24 -06:00
parent 236c678ccb
commit 8dc9ee6cf2
12 changed files with 390 additions and 20 deletions
+2
View File
@@ -105,6 +105,7 @@ struct ZombieFile: Identifiable, Equatable, Hashable {
enum CurrentTabView:Int
{
case general
case menubar
// case permissions
// case sentinel
case update
@@ -113,6 +114,7 @@ enum CurrentTabView:Int
var title: String {
switch self {
case .general: return "General"
case .menubar: return "MenuBar"
// case .permissions: return "Permissions"
// case .sentinel: return "Sentinel"
case .update: return "Update"
+60
View File
@@ -0,0 +1,60 @@
//
// MenuBarItem.swift
// Pearcleaner
//
// Created by Alin Lupascu on 3/16/24.
//
import AppKit
import SwiftUI
class MenuBarExtraManager {
static let shared = MenuBarExtraManager()
private var statusItem: NSStatusItem?
private var popover = NSPopover()
func addMenuBarExtra<V: View>(withView view: @escaping () -> V) {
guard statusItem == nil else { return }
// 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: "menubar.rectangle", accessibilityDescription: "Pearcleaner")
button.action = #selector(togglePopover(_:))
button.target = self
}
// Set up the popover
popover.contentSize = NSSize(width: 300, height: 370)
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: view())
}
func removeMenuBarExtra() {
if let item = statusItem {
NSStatusBar.system.removeStatusItem(item)
statusItem = nil
}
}
func getStatus() -> Bool {
if let item = statusItem {
return true
} else {
return false
}
}
@objc func togglePopover(_ sender: AnyObject?) {
if let button = statusItem?.button {
if popover.isShown {
popover.performClose(sender)
} else {
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
}
+18
View File
@@ -15,6 +15,7 @@ class WindowSettings {
private let windowXKey = "windowXKey"
private let windowYKey = "windowYKey"
@AppStorage("settings.general.mini") private var mini: Bool = false
var window: NSWindow?
func saveWindowSettings(frame: NSRect) {
@@ -32,4 +33,21 @@ class WindowSettings {
let y = CGFloat(UserDefaults.standard.float(forKey: windowYKey))
return NSRect(x: x, y: y, width: width, height: height)
}
func newWindow<V: View>(withView view: @escaping () -> V) {
let contentView = view
let frame = self.loadWindowSettings()
// Create the window and set the content view
let newWindow = NSWindow(
contentRect: frame,
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
newWindow.titlebarAppearsTransparent = true
newWindow.isMovableByWindowBackground = true
newWindow.center()
newWindow.setFrameAutosaveName("Main Window")
newWindow.contentView = NSHostingView(rootView: contentView())
self.window = newWindow
newWindow.makeKeyAndOrderFront(nil)
}
}
+35 -6
View File
@@ -7,6 +7,7 @@
import SwiftUI
import AppKit
import ServiceManagement
@main
struct PearcleanerApp: App {
@@ -24,10 +25,15 @@ struct PearcleanerApp: App {
@AppStorage("settings.general.instant") private var instantSearch: Bool = true
@AppStorage("settings.general.features") private var features: String = ""
@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
@State private var search = ""
@State private var showPopover: Bool = false
@State private var showFeature: Bool = false
var body: some Scene {
WindowGroup {
@@ -100,6 +106,15 @@ struct PearcleanerApp: App {
loadAllPaths(allApps: sortedApps, appState: appState, locations: locations)
}
if menubarEnabled {
MenuBarExtraManager.shared.addMenuBarExtra {
MenuBarMiniAppView(search: $search, showPopover: $showPopover)
.environmentObject(locations)
.environmentObject(appState)
}
}
#if !DEBUG
Task {
@@ -135,6 +150,7 @@ struct PearcleanerApp: App {
#endif
}
}
.windowStyle(.hiddenTitleBar)
.windowResizability(.contentMinSize)
.commands {
@@ -143,10 +159,13 @@ struct PearcleanerApp: App {
}
Settings {
SettingsView(showPopover: $showPopover, showFeature: $showFeature)
SettingsView(showPopover: $showPopover, search: $search, showFeature: $showFeature)
.environmentObject(appState)
.environmentObject(locations)
.toolbarBackground(.clear)
.preferredColorScheme(displayMode.colorScheme)
}
@@ -157,14 +176,24 @@ struct PearcleanerApp: App {
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
return !menubarEnabled
}
// func applicationDidFinishLaunching(_ notification: Notification, win: WindowSettings) {
// let frame = win.loadWindowSettings()
// NSApplication.shared.windows.first?.setFrame(frame, display: true)
// }
func applicationDidFinishLaunching(_ notification: Notification) {
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
#if !DEBUG
if menubarEnabled {
NSApp.windows.first?.close()
NSApplication.shared.setActivationPolicy(.accessory)
}
#endif
}
}
+6 -8
View File
@@ -7,9 +7,11 @@
import Foundation
import SwiftUI
import ServiceManagement
struct GeneralSettingsTab: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations
@State private var windowSettings = WindowSettings()
@AppStorage("settings.general.glass") private var glass: Bool = true
@AppStorage("settings.general.mini") private var mini: Bool = false
@@ -26,6 +28,7 @@ struct GeneralSettingsTab: View {
@State private var accessStatus: Bool = false
private let themes = ["Auto", "Dark", "Light"]
@Binding var showPopover: Bool
@Binding var search: String
var body: some View {
Form {
@@ -148,7 +151,7 @@ struct GeneralSettingsTab: View {
.padding(5)
.padding(.leading)
// =========================================================================================================
// === Mini =================================================================================================
Divider()
.padding()
@@ -247,7 +250,7 @@ struct GeneralSettingsTab: View {
.padding(.leading)
// =========================================================================================================
// === Perms ================================================================================================
Divider()
@@ -312,7 +315,7 @@ struct GeneralSettingsTab: View {
.padding(.leading)
// =========================================================================================================
// === Sentinel =============================================================================================
Divider()
.padding()
@@ -362,11 +365,6 @@ struct GeneralSettingsTab: View {
.onAppear {
diskStatus = checkAndRequestFullDiskAccess(appState: appState, skipAlert: true)
accessStatus = checkAndRequestAccessibilityAccess(appState: appState)
// if displayMode.colorScheme == .dark {
// selectedTheme = "Dark"
// } else if displayMode.colorScheme == .light {
// selectedTheme = "Light"
// }
}
}
+112
View File
@@ -0,0 +1,112 @@
//
// MenuBar.swift
// Pearcleaner
//
// Created by Alin Lupascu on 3/16/24.
//
import Foundation
import SwiftUI
import ServiceManagement
struct MenuBarSettingsTab: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
@State private var isLaunchAtLoginEnabled: Bool = false
@Binding var showPopover: Bool
@Binding var search: String
var body: some View {
Form {
VStack {
HStack() {
Text("Configuration").font(.title2)
Spacer()
}
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: "menubar.rectangle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(menubarEnabled ? "Menubar icon enabled" : "Menubar icon disabled")")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $menubarEnabled, label: {
})
.toggleStyle(.switch)
.onChange(of: menubarEnabled) { newVal in
if newVal {
MenuBarExtraManager.shared.addMenuBarExtra {
MenuBarMiniAppView(search: $search, showPopover: $showPopover)
.environmentObject(locations)
.environmentObject(appState)
}
} else {
MenuBarExtraManager.shared.removeMenuBarExtra()
}
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: isLaunchAtLoginEnabled ? "person.crop.circle.badge.checkmark" : "person.crop.circle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(isLaunchAtLoginEnabled ? "Launch at login enabled" : "Launch at login disabled")")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $isLaunchAtLoginEnabled, label: {
})
.toggleStyle(.switch)
.onAppear {
isLaunchAtLoginEnabled = SMAppService.mainApp.status == .enabled
}
.onChange(of: isLaunchAtLoginEnabled) { newValue in
do {
if newValue {
if SMAppService.mainApp.status == .enabled {
try? SMAppService.mainApp.unregister()
}
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
} catch {
printOS("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
}
}
}
.padding(5)
.padding(.leading)
Spacer()
}
}
.padding(20)
.frame(width: 500, height: 690)
}
}
+11 -4
View File
@@ -10,30 +10,37 @@ import SwiftUI
struct SettingsView: View {
@EnvironmentObject var appState: AppState
@Binding var showPopover: Bool
@Binding var search: String
@Binding var showFeature: Bool
@AppStorage("settings.general.glass") private var glass: Bool = true
var body: some View {
TabView() {
GeneralSettingsTab(showPopover: $showPopover)
GeneralSettingsTab(showPopover: $showPopover, search: $search)
.tabItem {
Label(CurrentTabView.general.title, systemImage: "gear")
}
.tag(CurrentTabView.general)
MenuBarSettingsTab(showPopover: $showPopover, search: $search)
.tabItem {
Label(CurrentTabView.menubar.title, systemImage: "menubar.rectangle")
}
.tag(CurrentTabView.menubar)
UpdateSettingsTab(showFeature: $showFeature)
.tabItem {
Label(CurrentTabView.update.title, systemImage: "cloud")
}
.tag(CurrentTabView.update)
AboutSettingsTab()
.tabItem {
Label(CurrentTabView.about.title, systemImage: "info.circle")
}
.tag(CurrentTabView.about)
}
.background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil)
+130
View File
@@ -0,0 +1,130 @@
//
// MenuBarAppsListView.swift
// Pearcleaner
//
// Created by Alin Lupascu on 3/16/24.
//
import Foundation
import SwiftUI
struct MenuBarMiniAppView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations
@State private var windowSettings = WindowSettings()
@State private var animateGradient: Bool = false
@Binding var search: String
@State private var showSys: Bool = true
@State private var showUsr: Bool = true
@AppStorage("settings.general.mini") private var mini: Bool = false
@AppStorage("settings.general.instant") private var instantSearch: Bool = true
@AppStorage("settings.general.glass") private var glass: Bool = true
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
@Binding var showPopover: Bool
var body: some View {
var filteredApps: [AppInfo] {
if search.isEmpty {
return appState.sortedApps
} else {
return appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
}
}
ZStack {
HStack(spacing: 0){
if appState.reload {
VStack {
Spacer()
ProgressView(instantSearch ? "Refreshing app list and caching files" : "Refreshing app list")
if instantSearch {
Image(systemName: "bolt.fill")
.resizable()
.scaledToFit()
.foregroundStyle(Color("mode").opacity(0.5))
.frame(width: 30, height: 30)
.padding()
}
Spacer()
}
.padding(.vertical)
} else {
VStack(alignment: .center) {
AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps).padding(0)
HStack(spacing: 10) {
if #available(macOS 14.0, *) {
SettingsLink()
.buttonStyle(SimpleButtonStyle(icon: "gear", help: "Settings", color: Color("mode")))
} else {
Button("Settings") {
NSApp.sendAction(Selector(("showPreferencesWindow:")), to: NSApp.delegate, from: nil)
}
.buttonStyle(SimpleButtonStyle(icon: "gear", help: "Settings", color: Color("mode")))
}
SearchBarMiniBottom(search: $search)
Button("Main") {
windowSettings.newWindow {
MiniMode(search: $search, showPopover: $showPopover)
.environmentObject(locations)
.environmentObject(appState)
}
}
.buttonStyle(SimpleButtonStyle(icon: "macwindow", help: "Show Main Window", color: Color("mode")))
Button("Kill") {
NSApp.terminate(nil)
}
.buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Quit", color: Color("mode")))
}
.padding(.horizontal)
}
.padding(.vertical, 5)
.padding(.bottom, 8)
}
}
}
.frame(minWidth: 300, minHeight: 370)
.edgesIgnoringSafeArea(.all)
.background(
Group {
if glass {
GlassEffect(material: .sidebar, blendingMode: .behindWindow)
.edgesIgnoringSafeArea(.all)
} else {
Color("pop")
.padding(-80)
}
}
)
.transition(.opacity)
.popover(isPresented: $showPopover, arrowEdge: .trailing) {
VStack {
if appState.currentView == .files {
FilesView(showPopover: $showPopover, search: $search)
.id(appState.appInfo.id)
} else if appState.currentView == .zombie {
ZombieView(showPopover: $showPopover, search: $search)
.id(appState.appInfo.id)
}
}
.interactiveDismissDisabled(popoverStay)
.background(
Rectangle()
.fill(Color("pop"))
.padding(-80)
)
.frame(width: 650, height: 550)
}
}
}
+1
View File
@@ -183,6 +183,7 @@ struct MiniAppView: View {
if appState.currentView != .empty {
SearchBarMiniBottom(search: $search)
.padding(.horizontal)
}
}
.padding(.bottom)
+1
View File
@@ -56,6 +56,7 @@ struct RegularMode: View {
VStack(alignment: .center, spacing: 20) {
HStack {
SearchBarMiniBottom(search: $search)
.padding(.horizontal)
}
}
+2 -2
View File
@@ -133,7 +133,7 @@ struct SearchBarMiniBottom: View {
TextField(" Search", text: $search)
.textFieldStyle(SimpleSearchStyle(trash: true, text: $search))
}
.padding(.horizontal)
.padding(.bottom, 0)
// .padding(.horizontal)
// .padding(.bottom, 0)
}
}