This commit is contained in:
Alin
2024-05-05 19:44:37 -06:00
parent 86005a6c93
commit 52496bb9a1
19 changed files with 278 additions and 246 deletions
+4
View File
@@ -39,6 +39,7 @@
C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A9CE462B89164700EB6E78 /* Authorization.swift */; }; C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A9CE462B89164700EB6E78 /* Authorization.swift */; };
C7ACADE62B92A737000B5845 /* Features.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ACADE52B92A737000B5845 /* Features.swift */; }; C7ACADE62B92A737000B5845 /* Features.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ACADE52B92A737000B5845 /* Features.swift */; };
C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CF47232B3B3F1700979C5F /* WindowSettings.swift */; }; C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CF47232B3B3F1700979C5F /* WindowSettings.swift */; };
C7D15BBE2BE85F4D007D57CF /* FeatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D15BBD2BE85F4D007D57CF /* FeatureView.swift */; };
C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.swift */; }; C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.swift */; };
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D502AFF00F300C7ED9E /* Locations.swift */; }; C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D502AFF00F300C7ED9E /* Locations.swift */; };
C7DC1FEE2B9F7D3D009AC317 /* features.json in Resources */ = {isa = PBXBuildFile; fileRef = C7DC1FED2B9F7D3D009AC317 /* features.json */; }; C7DC1FEE2B9F7D3D009AC317 /* features.json in Resources */ = {isa = PBXBuildFile; fileRef = C7DC1FED2B9F7D3D009AC317 /* features.json */; };
@@ -135,6 +136,7 @@
C7A9CE462B89164700EB6E78 /* Authorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = "<group>"; }; C7A9CE462B89164700EB6E78 /* Authorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = "<group>"; };
C7ACADE52B92A737000B5845 /* Features.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features.swift; sourceTree = "<group>"; }; C7ACADE52B92A737000B5845 /* Features.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features.swift; sourceTree = "<group>"; };
C7CF47232B3B3F1700979C5F /* WindowSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowSettings.swift; sourceTree = "<group>"; }; C7CF47232B3B3F1700979C5F /* WindowSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowSettings.swift; sourceTree = "<group>"; };
C7D15BBD2BE85F4D007D57CF /* FeatureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureView.swift; sourceTree = "<group>"; };
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = "<group>"; }; C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = "<group>"; };
C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = "<group>"; }; C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = "<group>"; };
C7DC1FED2B9F7D3D009AC317 /* features.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = features.json; sourceTree = SOURCE_ROOT; }; C7DC1FED2B9F7D3D009AC317 /* features.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = features.json; sourceTree = SOURCE_ROOT; };
@@ -181,6 +183,7 @@
children = ( children = (
C7045A312B068AD700376976 /* NewWin.swift */, C7045A312B068AD700376976 /* NewWin.swift */,
C7045A332B068B2900376976 /* UpdateView.swift */, C7045A332B068B2900376976 /* UpdateView.swift */,
C7D15BBD2BE85F4D007D57CF /* FeatureView.swift */,
C7045A352B068DD700376976 /* PermView.swift */, C7045A352B068DD700376976 /* PermView.swift */,
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */, C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */,
C7CF47232B3B3F1700979C5F /* WindowSettings.swift */, C7CF47232B3B3F1700979C5F /* WindowSettings.swift */,
@@ -492,6 +495,7 @@
C7ACADE62B92A737000B5845 /* Features.swift in Sources */, C7ACADE62B92A737000B5845 /* Features.swift in Sources */,
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */, C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */,
C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */, C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */,
C7D15BBE2BE85F4D007D57CF /* FeatureView.swift in Sources */,
C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */, C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */,
C7FEBA112BDC422200AE195F /* AppSearchView.swift in Sources */, C7FEBA112BDC422200AE195F /* AppSearchView.swift in Sources */,
C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */, C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */,
+1 -1
View File
@@ -50,7 +50,7 @@ struct AppCommands: Commands {
.keyboardShortcut("r", modifiers: .command) .keyboardShortcut("r", modifiers: .command)
Button { Button {
uninstallPearcleaner(appState: appState, locations: locations) appState.triggerUninstallAlert()
} label: { } label: {
Text("Uninstall Pearcleaner") Text("Uninstall Pearcleaner")
} }
-1
View File
@@ -310,7 +310,6 @@ class AppPathFinder {
self.appState.appInfo = self.appInfo self.appState.appInfo = self.appInfo
if !self.undo { if !self.undo {
self.appState.selectedItems = Set(updatedCollection) self.appState.selectedItems = Set(updatedCollection)
} }
} }
+9 -2
View File
@@ -11,8 +11,7 @@ import FinderSync
let home = FileManager.default.homeDirectoryForCurrentUser.path let home = FileManager.default.homeDirectoryForCurrentUser.path
class AppState: ObservableObject class AppState: ObservableObject {
{
@Published var appInfo: AppInfo @Published var appInfo: AppInfo
@Published var appInfoStore: [AppInfo] = [] @Published var appInfoStore: [AppInfo] = []
@Published var trashedFiles: [AppInfo] = [] @Published var trashedFiles: [AppInfo] = []
@@ -29,8 +28,10 @@ class AppState: ObservableObject
@Published var showProgress: Bool = false @Published var showProgress: Bool = false
@Published var finderExtensionEnabled: Bool = false @Published var finderExtensionEnabled: Bool = false
@Published var updateAvailable: Bool = false @Published var updateAvailable: Bool = false
@Published var featureAvailable: Bool = false
@Published var permissionsOkay: Bool = true @Published var permissionsOkay: Bool = true
@Published var permissionResults: PermissionsCheckResults? @Published var permissionResults: PermissionsCheckResults?
@Published var showUninstallAlert: Bool = false
@@ -74,6 +75,11 @@ class AppState: ObservableObject
self.finderExtensionEnabled = extensionStatus self.finderExtensionEnabled = extensionStatus
} }
} }
func triggerUninstallAlert() {
self.showUninstallAlert = true
}
} }
@@ -166,6 +172,7 @@ enum NewWindow:Int
case update case update
case no_update case no_update
case perm case perm
case feature
} }
+2 -2
View File
@@ -110,8 +110,8 @@ let skipConditions: [SkipCondition] = [
] ]
// Skip files/folders during reverse file search // Skip files/folders during leftover file search
let skipReverse = ["apple", "temporary", "btserver", "proapps", "scripteditor", "ilife", "livefsd", "siritoday", "addressbook", "animoji", "appstore", "askpermission", "callhistory", "clouddocs", "diskimages", "dock", "facetime", "fileprovider", "instruments", "knowledge", "mobilesync", "syncservices", "homeenergyd", "icloud", "icdd", "networkserviceproxy", "familycircle", "geoservices", "installation", "passkit", "sharedimagecache", "desktop", "mbuseragent", "swiftpm", "baseband", "coresimulator", "photoslegacyupgrade", "photosupgrade", "siritts", "ipod", "globalpreferences", "apmanalytics", "apmexperiment", "avatarcache", "byhost", "contextstoreagent", "mobilemeaccounts", "intentbuilderc", "loginwindow", "momc", "replayd", "sharedfilelistd", "clang", "audiocomponent", "csexattrcryptoservice", "livetranscriptionagent", "sandboxhelper", "statuskitagent", "betaenrollmentd", "contentlinkingd", "diagnosticextensionsd", "gamed", "heard", "homed", "itunescloudd", "lldb", "mds", "mediaanalysisd", "metrickitd", "mobiletimerd", "proactived", "ptpcamerad", "studentd", "talagent", "watchlistd", "apptranslocation", "xcrun", "ds_store", "caches", "crashreporter", "trash", "pearcleaner", "amsdatamigratortool", "arfilecache", "assistant", "chromium", "cloudkit", "webkit", "databases", "diagnostic", "cache", "gamekit", "homebrew", "logi", "microsoft", "mozilla", "sync", "google", "sentinel", "hexnode", "sentry", "tvappservices"] let skipReverse = ["apple", "temporary", "btserver", "proapps", "scripteditor", "ilife", "livefsd", "siritoday", "addressbook", "animoji", "appstore", "askpermission", "callhistory", "clouddocs", "diskimages", "dock", "facetime", "fileprovider", "instruments", "knowledge", "mobilesync", "syncservices", "homeenergyd", "icloud", "icdd", "networkserviceproxy", "familycircle", "geoservices", "installation", "passkit", "sharedimagecache", "desktop", "mbuseragent", "swiftpm", "baseband", "coresimulator", "photoslegacyupgrade", "photosupgrade", "siritts", "ipod", "globalpreferences", "apmanalytics", "apmexperiment", "avatarcache", "byhost", "contextstoreagent", "mobilemeaccounts", "intentbuilderc", "loginwindow", "momc", "replayd", "sharedfilelistd", "clang", "audiocomponent", "csexattrcryptoservice", "livetranscriptionagent", "sandboxhelper", "statuskitagent", "betaenrollmentd", "contentlinkingd", "diagnosticextensionsd", "gamed", "heard", "homed", "itunescloudd", "lldb", "mds", "mediaanalysisd", "metrickitd", "mobiletimerd", "proactived", "ptpcamerad", "studentd", "talagent", "watchlistd", "apptranslocation", "xcrun", "ds_store", "caches", "crashreporter", "trash", "pearcleaner", "amsdatamigratortool", "arfilecache", "assistant", "chromium", "cloudkit", "webkit", "databases", "diagnostic", "cache", "gamekit", "homebrew", "logi", "microsoft", "mozilla", "sync", "google", "sentinel", "hexnode", "sentry", "tvappservices", "reminders"]
// Function to load additional conditions from a GitHub JSON file // Function to load additional conditions from a GitHub JSON file
+69 -7
View File
@@ -8,8 +8,7 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
func getFeatures(appState: AppState, show: Binding<Bool>, features: Binding<String>) { func getFeatures(appState: AppState, features: Binding<String>) {
let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/contents/features.json")! let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/contents/features.json")!
var request = URLRequest(url: url) var request = URLRequest(url: url)
request.setValue("application/vnd.github.VERSION.raw", forHTTPHeaderField: "Accept") request.setValue("application/vnd.github.VERSION.raw", forHTTPHeaderField: "Accept")
@@ -25,25 +24,88 @@ func getFeatures(appState: AppState, show: Binding<Bool>, features: Binding<Stri
if features.wrappedValue != featureText.featureFormat() { if features.wrappedValue != featureText.featureFormat() {
updateOnMain { updateOnMain {
features.wrappedValue = featureText.featureFormat() features.wrappedValue = featureText.featureFormat()
show.wrappedValue = true appState.featureAvailable = true
// show.wrappedValue = true
} }
} else { } else {
show.wrappedValue = false updateOnMain {
appState.featureAvailable = false
}
// show.wrappedValue = false
// print("Same version") // print("Same version")
} }
} else { } else {
show.wrappedValue = false updateOnMain {
appState.featureAvailable = false
}
// show.wrappedValue = false
// print("No features for version found") // print("No features for version found")
} }
} catch { } catch {
show.wrappedValue = false
printOS("Error reading features JSON from GitHub: \(error.localizedDescription)") printOS("Error reading features JSON from GitHub: \(error.localizedDescription)")
} }
} else { } else {
show.wrappedValue = false
printOS("Error reading features JSON from GitHub: \(error?.localizedDescription ?? "Unknown error")") printOS("Error reading features JSON from GitHub: \(error?.localizedDescription ?? "Unknown error")")
} }
}.resume() }.resume()
} }
struct FeatureNotificationView: View {
let appState: AppState
@State private var hovered: Bool = false
var body: some View {
HStack {
Text("New Features!")
.font(.callout)
.opacity(0.5)
.padding(.leading, 7)
Spacer()
HStack(alignment: .center) {
Image(systemName: !hovered ? "star" : "star.fill")
.resizable()
.scaledToFit()
.frame(width: 14, height: 14)
.animation(.easeInOut(duration: 0.2), value: hovered)
.foregroundStyle(.white)
Text("Show")
.foregroundStyle(.white)
}
.padding(3)
.onHover { hovering in
withAnimation() {
hovered = hovering
}
}
.onTapGesture {
NewWin.show(appState: appState, width: 500, height: 400, newWin: .feature)
updateOnMain {
appState.featureAvailable = false
}
}
.help("View latest features")
.padding(.horizontal, 5)
.padding(.vertical, 4)
.background(Color("pear"))
.clipShape(RoundedRectangle(cornerRadius: 6))
}
.frame(height: 30)
.padding(5)
.background(Color("mode").opacity(0.05))
.clipShape(RoundedRectangle(cornerRadius: 6))
.padding(.horizontal)
.padding(.bottom)
}
}
-1
View File
@@ -29,7 +29,6 @@ class Locations: ObservableObject {
"\(home)/Library", "\(home)/Library",
"\(home)/Library/Application Scripts", "\(home)/Library/Application Scripts",
"\(home)/Library/Application Support", "\(home)/Library/Application Support",
"\(home)/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments",
"\(home)/Library/Containers", "\(home)/Library/Containers",
"\(home)/Library/Caches", "\(home)/Library/Caches",
"\(home)/Library/Group Containers", "\(home)/Library/Group Containers",
+4 -29
View File
@@ -6,17 +6,16 @@
// //
import SwiftUI import SwiftUI
import EventKit //import EventKit
struct PermissionsCheckResults { struct PermissionsCheckResults {
var fullDiskAccess: Bool var fullDiskAccess: Bool
var accessibility: Bool var accessibility: Bool
var automation: Bool var automation: Bool
var reminders: Bool
// Computed property to check if all permissions are granted // Computed property to check if all permissions are granted
var allPermissionsGranted: Bool { var allPermissionsGranted: Bool {
return fullDiskAccess && accessibility && automation && reminders return fullDiskAccess && accessibility && automation
} }
} }
@@ -51,25 +50,17 @@ func checkAllPermissions(appState: AppState, completion: @escaping (PermissionsC
dispatchGroup.leave() dispatchGroup.leave()
} }
// Check Reminders Permission
var remindersAccess = false
dispatchGroup.enter()
checkRemindersAccess(appState: appState) { granted in
remindersAccess = granted
dispatchGroup.leave()
}
// Wait for all async checks to complete // Wait for all async checks to complete
dispatchGroup.notify(queue: .main) { dispatchGroup.notify(queue: .main) {
let results = PermissionsCheckResults( let results = PermissionsCheckResults(
fullDiskAccess: fullDiskAccess, fullDiskAccess: fullDiskAccess,
accessibility: accessibilityEnabled, accessibility: accessibilityEnabled,
automation: automationAccess, automation: automationAccess
reminders: remindersAccess
) )
// Check if any permission is denied and show window // Check if any permission is denied and show window
if !(results.fullDiskAccess && results.accessibility && results.automation && results.reminders) { if !(results.fullDiskAccess && results.accessibility && results.automation) {
updateOnMain { updateOnMain {
appState.permissionsOkay = false appState.permissionsOkay = false
} }
@@ -99,22 +90,6 @@ func checkAutomationPermission(appState: AppState, completion: @escaping (Bool)
} }
} }
// Check Reminders storage permission
func checkRemindersAccess(appState: AppState, completion: @escaping (Bool) -> Void) {
let eventStore = EKEventStore()
eventStore.requestAccess(to: .reminder) { granted, error in
DispatchQueue.main.async {
if error != nil {
completion(false)
} else {
completion(granted)
}
}
}
}
struct PermissionsNotificationView: View { struct PermissionsNotificationView: View {
+4 -14
View File
@@ -260,7 +260,7 @@ struct InfoButtonPerms: View {
} }
} }
.popover(isPresented: $isPopoverPresented, arrowEdge: .bottom) { .popover(isPresented: $isPopoverPresented, arrowEdge: .bottom) {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 15) {
HStack(alignment: .top, spacing: 20) { HStack(alignment: .top, spacing: 20) {
Image(systemName: "externaldrive") Image(systemName: "externaldrive")
@@ -268,7 +268,7 @@ struct InfoButtonPerms: View {
.scaledToFit() .scaledToFit()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
Text("Full Disk Access permission to find and delete files in system paths") Text("Full Disk permission to access files/folders in system paths")
.font(.callout) .font(.callout)
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
} }
@@ -279,7 +279,7 @@ struct InfoButtonPerms: View {
.scaledToFit() .scaledToFit()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
Text("Accessibility permission to delete files via Finder") Text("Accessibility permission to allow execution of AppleScript")
.font(.callout) .font(.callout)
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
} }
@@ -290,21 +290,11 @@ struct InfoButtonPerms: View {
.scaledToFit() .scaledToFit()
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
Text("Automation permission to perform extension actions via Finder") Text("Automation permission to perform delete actions via Finder")
.font(.callout) .font(.callout)
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
} }
HStack(alignment: .top, spacing: 20) {
Image(systemName: "calendar")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.foregroundStyle(Color("mode").opacity(0.5))
Text("Reminders permission to find and delete some reminder files that certain apps cache")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
}
} }
.padding() .padding()
} }
+3 -6
View File
@@ -123,7 +123,7 @@ func findAndHideWindows(named titles: [String]) {
func findAndSetWindowFrame(named titles: [String], windowSettings: WindowSettings) { func findAndSetWindowFrame(named titles: [String], windowSettings: WindowSettings) {
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 // Doing this via view window.isRestorable = false
let frame = windowSettings.loadWindowSettings() let frame = windowSettings.loadWindowSettings()
window.setFrame(frame, display: true) window.setFrame(frame, display: true)
} }
@@ -561,10 +561,8 @@ func uninstallPearcleaner(appState: AppState, locations: Locations) {
let appInfo = AppInfoFetcher.getAppInfo(atPath: Bundle.main.bundleURL) let appInfo = AppInfoFetcher.getAppInfo(atPath: Bundle.main.bundleURL)
// Find application files for Pearcleaner // Find application files for Pearcleaner
AppPathFinder(appInfo: appInfo!, appState: appState, locations: locations).findPaths() AppPathFinder(appInfo: appInfo!, appState: appState, locations: locations, completion: {
// Kill Pearcleaner and tell Finder to trash the files // Kill Pearcleaner and tell Finder to trash the files
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
let selectedItemsArray = Array(appState.selectedItems).filter { !$0.path.contains(".Trash") } let selectedItemsArray = Array(appState.selectedItems).filter { !$0.path.contains(".Trash") }
let posixFiles = selectedItemsArray.map { "POSIX file \"\($0.path)\", " }.joined().dropLast(3) let posixFiles = selectedItemsArray.map { "POSIX file \"\($0.path)\", " }.joined().dropLast(3)
let scriptSource = """ let scriptSource = """
@@ -577,8 +575,7 @@ func uninstallPearcleaner(appState: AppState, locations: Locations) {
NSApp.terminate(nil) NSApp.terminate(nil)
exit(0) exit(0)
} }).findPaths()
} }
+13 -20
View File
@@ -26,10 +26,8 @@ struct PearcleanerApp: App {
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
@AppStorage("settings.menubar.mainWin") private var mainWinEnabled: Bool = false @AppStorage("settings.menubar.mainWin") private var mainWinEnabled: Bool = false
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "trash" @AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "trash"
@State private var search = "" @State private var search = ""
@State private var showPopover: Bool = false @State private var showPopover: Bool = false
@State private var showFeature: Bool = false
@@ -37,21 +35,11 @@ struct PearcleanerApp: App {
WindowGroup { WindowGroup {
Group { Group {
ZStack() {
if !mini { if !mini {
RegularMode(search: $search, showPopover: $showPopover) RegularMode(search: $search, showPopover: $showPopover)
} else { } else {
MiniMode(search: $search, showPopover: $showPopover) MiniMode(search: $search, showPopover: $showPopover)
} }
if showFeature {
NewFeatureView(text: features, mini: mini, showFeature: $showFeature)
.transition(.opacity)
}
}
} }
.environmentObject(appState) .environmentObject(appState)
.environmentObject(locations) .environmentObject(locations)
@@ -80,6 +68,16 @@ struct PearcleanerApp: App {
windowSettings.saveWindowSettings(frame: newFrame) windowSettings.saveWindowSettings(frame: newFrame)
} }
} }
.alert(isPresented: $appState.showUninstallAlert) {
Alert(
title: Text("Warning!"),
message: Text("Pearcleaner and all of its files will be cleanly removed, are you sure?"),
primaryButton: .destructive(Text("Uninstall")) {
uninstallPearcleaner(appState: appState, locations: locations)
},
secondaryButton: .cancel()
)
}
.onAppear { .onAppear {
if miniView { if miniView {
@@ -91,10 +89,6 @@ struct PearcleanerApp: App {
// Disable tabbing // Disable tabbing
NSWindow.allowsAutomaticWindowTabbing = false NSWindow.allowsAutomaticWindowTabbing = false
// Set window size on load
// findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings)
// Get Apps // Get Apps
let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState) let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState)
appState.sortedApps = sortedApps appState.sortedApps = sortedApps
@@ -112,7 +106,7 @@ struct PearcleanerApp: App {
} }
#if DEBUG #if !DEBUG
Task { Task {
@@ -124,7 +118,7 @@ struct PearcleanerApp: App {
appState.permissionResults = results appState.permissionResults = results
if results.allPermissionsGranted { if results.allPermissionsGranted {
loadGithubReleases(appState: appState) loadGithubReleases(appState: appState)
getFeatures(appState: appState, show: $showFeature, features: $features) getFeatures(appState: appState, features: $features)
} }
} }
@@ -159,14 +153,13 @@ struct PearcleanerApp: App {
Settings { Settings {
SettingsView(showPopover: $showPopover, search: $search, showFeature: $showFeature) SettingsView(showPopover: $showPopover, search: $search)
.environmentObject(appState) .environmentObject(appState)
.environmentObject(locations) .environmentObject(locations)
.environmentObject(fsm) .environmentObject(fsm)
.environmentObject(ThemeSettings.shared) .environmentObject(ThemeSettings.shared)
.toolbarBackground(.clear) .toolbarBackground(.clear)
.preferredColorScheme(displayMode.colorScheme) .preferredColorScheme(displayMode.colorScheme)
.willRestore()
} }
} }
} }
+11 -69
View File
@@ -28,7 +28,6 @@ struct GeneralSettingsTab: View {
@State private var diskStatus: Bool = false @State private var diskStatus: Bool = false
@State private var accessStatus: Bool = false @State private var accessStatus: Bool = false
@State private var autoStatus: Bool = false @State private var autoStatus: Bool = false
@State private var remStatus: Bool = false
@Binding var showPopover: Bool @Binding var showPopover: Bool
@Binding var search: String @Binding var search: String
@State var selectedIndex: Int? @State var selectedIndex: Int?
@@ -182,6 +181,8 @@ struct GeneralSettingsTab: View {
InfoButtonPerms() InfoButtonPerms()
Spacer()
Button("Refresh") { Button("Refresh") {
checkAllPermissions(appState: appState) { results in checkAllPermissions(appState: appState) { results in
updateOnMain { updateOnMain {
@@ -190,7 +191,6 @@ struct GeneralSettingsTab: View {
diskStatus = results.fullDiskAccess diskStatus = results.fullDiskAccess
accessStatus = results.accessibility accessStatus = results.accessibility
autoStatus = results.automation autoStatus = results.automation
remStatus = results.reminders
if results.allPermissionsGranted { if results.allPermissionsGranted {
updateOnMain { updateOnMain {
appState.permissionsOkay = true appState.permissionsOkay = true
@@ -199,14 +199,12 @@ struct GeneralSettingsTab: View {
} }
} }
.buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh permissions")) .buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh permissions"))
.padding(.trailing, 5)
Spacer()
} }
.padding(.leading) .padding(.leading)
HStack {
HStack(spacing: 0) { HStack(spacing: 0) {
Image(systemName: "externaldrive") Image(systemName: "externaldrive")
.resizable() .resizable()
@@ -215,10 +213,9 @@ struct GeneralSettingsTab: View {
.padding(.trailing) .padding(.trailing)
.foregroundStyle(diskStatus ? .green : .red) .foregroundStyle(diskStatus ? .green : .red)
.saturation(displayMode.colorScheme == .dark ? 0.5 : 1) .saturation(displayMode.colorScheme == .dark ? 0.5 : 1)
Text("Full Disk") Text(diskStatus ? "Full Disk permission granted" : "Full Disk permission not granted")
.font(.callout) .font(.callout)
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
.frame(width: 100)
Spacer() Spacer()
@@ -227,14 +224,11 @@ struct GeneralSettingsTab: View {
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
} }
} }
.buttonStyle(SimpleButtonStyle(icon: "arrow.right.circle.fill", help: "View disk permissions pane", size: 14)) .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View disk permissions pane"))
Spacer()
} }
.padding(5) .padding(5)
.padding(.leading) .padding(.leading)
.frame(width: 200)
Spacer() Spacer()
@@ -247,10 +241,9 @@ struct GeneralSettingsTab: View {
.padding(.trailing) .padding(.trailing)
.foregroundStyle(accessStatus ? .green : .red) .foregroundStyle(accessStatus ? .green : .red)
.saturation(displayMode.colorScheme == .dark ? 0.5 : 1) .saturation(displayMode.colorScheme == .dark ? 0.5 : 1)
Text("Accessibility") Text(accessStatus ? "Accessibility permission granted" : "Accessibility permission not granted")
.font(.callout) .font(.callout)
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
.frame(width: 100)
Spacer() Spacer()
@@ -259,19 +252,14 @@ struct GeneralSettingsTab: View {
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
} }
} }
.buttonStyle(SimpleButtonStyle(icon: "arrow.right.circle.fill", help: "View accessibility permissions pane", size: 14)) .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View accessibility permissions pane"))
Spacer()
} }
.padding(5) .padding(5)
.padding(.leading) .padding(.leading)
.frame(width: 200)
} Spacer()
HStack {
HStack(spacing: 0) { HStack(spacing: 0) {
Image(systemName: "gearshape.2") Image(systemName: "gearshape.2")
.resizable() .resizable()
@@ -280,10 +268,9 @@ struct GeneralSettingsTab: View {
.padding(.trailing) .padding(.trailing)
.foregroundStyle(autoStatus ? .green : .red) .foregroundStyle(autoStatus ? .green : .red)
.saturation(displayMode.colorScheme == .dark ? 0.5 : 1) .saturation(displayMode.colorScheme == .dark ? 0.5 : 1)
Text("Automation") Text(autoStatus ? "Automation permission granted" : "Automation permission not granted")
.font(.callout) .font(.callout)
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
.frame(width: 100)
Spacer() Spacer()
@@ -292,49 +279,11 @@ struct GeneralSettingsTab: View {
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
} }
} }
.buttonStyle(SimpleButtonStyle(icon: "arrow.right.circle.fill", help: "View automation permissions pane", size: 14)) .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View automation permissions pane"))
Spacer()
} }
.padding(5) .padding(5)
.padding(.leading) .padding(.leading)
.frame(width: 200)
Spacer()
HStack(spacing: 0) {
Image(systemName: "calendar")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(remStatus ? .green : .red)
.saturation(displayMode.colorScheme == .dark ? 0.5 : 1)
Text("Reminders")
.font(.callout)
.foregroundStyle(Color("mode").opacity(0.5))
.frame(width: 100)
Spacer()
Button("") {
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Reminders") {
NSWorkspace.shared.open(url)
}
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.right.circle.fill", help: "View reminders permissions pane", size: 14))
Spacer()
}
.padding(5)
.padding(.leading)
.frame(width: 200)
}
@@ -445,7 +394,6 @@ struct GeneralSettingsTab: View {
diskStatus = results.fullDiskAccess diskStatus = results.fullDiskAccess
accessStatus = results.accessibility accessStatus = results.accessibility
autoStatus = results.automation autoStatus = results.automation
remStatus = results.reminders
if results.allPermissionsGranted { if results.allPermissionsGranted {
updateOnMain { updateOnMain {
appState.permissionsOkay = true appState.permissionsOkay = true
@@ -457,24 +405,18 @@ struct GeneralSettingsTab: View {
} }
.padding(20) .padding(20)
.frame(width: 500, height: 650) .frame(width: 500, height: 670)
} }
private var keysToIgnore: Set<String> {
["windowWidthKey", "windowHeightKey", "windowWidthKeyMini", "windowHeightKeyMini", "windowXKey", "windowYKey"]
}
private func resetUserDefaults() { private func resetUserDefaults() {
isResetting = true isResetting = true
DispatchQueue.global(qos: .background).async { DispatchQueue.global(qos: .background).async {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
let dictionary = defaults.dictionaryRepresentation() let dictionary = defaults.dictionaryRepresentation()
dictionary.keys.forEach { key in dictionary.keys.forEach { key in
if !keysToIgnore.contains(key) {
defaults.removeObject(forKey: key) defaults.removeObject(forKey: key)
} }
}
DispatchQueue.main.async { DispatchQueue.main.async {
isResetting = false isResetting = false
} }
+12 -13
View File
@@ -306,26 +306,23 @@ struct InterfaceSettingsTab: View {
if mini { if mini {
appState.currentView = miniView ? .apps : .empty appState.currentView = miniView ? .apps : .empty
showPopover = false showPopover = false
findAndHideWindows(named: ["Pearcleaner"]) windowSettings.newWindow(withView: {
windowSettings.newWindow {
MiniMode(search: $search, showPopover: $showPopover) MiniMode(search: $search, showPopover: $showPopover)
.environmentObject(locations) .environmentObject(locations)
.environmentObject(appState) .environmentObject(appState)
.environmentObject(fsm) .environmentObject(fsm)
.environmentObject(ThemeSettings.shared) .environmentObject(ThemeSettings.shared)
.preferredColorScheme(displayMode.colorScheme) .preferredColorScheme(displayMode.colorScheme)
} }) {
updateOnMain(after: 0.1, {
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
}) }
} else { } else {
if appState.appInfo.appName.isEmpty { if appState.appInfo.appName.isEmpty {
appState.currentView = .empty appState.currentView = .empty
} else { } else {
appState.currentView = .files appState.currentView = .files
} }
findAndHideWindows(named: ["Pearcleaner"]) windowSettings.newWindow(withView: {
windowSettings.newWindow {
RegularMode(search: $search, showPopover: $showPopover) RegularMode(search: $search, showPopover: $showPopover)
.environmentObject(locations) .environmentObject(locations)
.environmentObject(appState) .environmentObject(appState)
@@ -333,9 +330,9 @@ struct InterfaceSettingsTab: View {
.environmentObject(ThemeSettings.shared) .environmentObject(ThemeSettings.shared)
.preferredColorScheme(displayMode.colorScheme) .preferredColorScheme(displayMode.colorScheme)
} }
updateOnMain(after: 0.1, { ) {
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
}) }
} }
@@ -443,27 +440,29 @@ struct InterfaceSettingsTab: View {
NSApplication.shared.setActivationPolicy(.regular) NSApplication.shared.setActivationPolicy(.regular)
if !hasWindowOpen() { if !hasWindowOpen() {
if mini { if mini {
windowSettings.newWindow { windowSettings.newWindow(withView: {
MiniMode(search: $search, showPopover: $showPopover) MiniMode(search: $search, showPopover: $showPopover)
.environmentObject(locations) .environmentObject(locations)
.environmentObject(appState) .environmentObject(appState)
.environmentObject(fsm) .environmentObject(fsm)
.environmentObject(ThemeSettings.shared) .environmentObject(ThemeSettings.shared)
.preferredColorScheme(displayMode.colorScheme) .preferredColorScheme(displayMode.colorScheme)
} }) {
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
}
} else { } else {
windowSettings.newWindow { windowSettings.newWindow(withView: {
RegularMode(search: $search, showPopover: $showPopover) RegularMode(search: $search, showPopover: $showPopover)
.environmentObject(locations) .environmentObject(locations)
.environmentObject(appState) .environmentObject(appState)
.environmentObject(fsm) .environmentObject(fsm)
.environmentObject(ThemeSettings.shared) .environmentObject(ThemeSettings.shared)
.preferredColorScheme(displayMode.colorScheme) .preferredColorScheme(displayMode.colorScheme)
} }) {
resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner")
} }
} }
}
} }
} }
+1 -2
View File
@@ -13,7 +13,6 @@ struct SettingsView: View {
@EnvironmentObject var themeSettings: ThemeSettings @EnvironmentObject var themeSettings: ThemeSettings
@Binding var showPopover: Bool @Binding var showPopover: Bool
@Binding var search: String @Binding var search: String
@Binding var showFeature: Bool
@AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.glass") private var glass: Bool = false
@AppStorage("settings.general.selectedTab") private var selectedTab: CurrentTabView = .general @AppStorage("settings.general.selectedTab") private var selectedTab: CurrentTabView = .general
@@ -38,7 +37,7 @@ struct SettingsView: View {
} }
.tag(CurrentTabView.folders) .tag(CurrentTabView.folders)
UpdateSettingsTab(showFeature: $showFeature) UpdateSettingsTab()
.tabItem { .tabItem {
Label(CurrentTabView.update.title, systemImage: "cloud") Label(CurrentTabView.update.title, systemImage: "cloud")
} }
+1 -2
View File
@@ -14,7 +14,6 @@ struct UpdateSettingsTab: View {
@State private var showAlert = false @State private var showAlert = false
@State private var showDone = false @State private var showDone = false
@AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1 @AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1
@Binding var showFeature: Bool
var body: some View { var body: some View {
VStack { VStack {
@@ -61,7 +60,7 @@ struct UpdateSettingsTab: View {
Spacer() Spacer()
Button(""){ Button(""){
showFeature.toggle() NewWin.show(appState: appState, width: 500, height: 440, newWin: .feature)
} }
.buttonStyle(SimpleButtonStyle(icon: "star", help: "Show last feature alert")) .buttonStyle(SimpleButtonStyle(icon: "star", help: "Show last feature alert"))
+2
View File
@@ -32,6 +32,8 @@ struct AppSearchView: View {
UpdateNotificationView(appState: appState) UpdateNotificationView(appState: appState)
} else if !appState.permissionsOkay { } else if !appState.permissionsOkay {
PermissionsNotificationView(appState: appState) PermissionsNotificationView(appState: appState)
} else if appState.featureAvailable {
FeatureNotificationView(appState: appState)
} }
AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps) AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps)
+59
View File
@@ -0,0 +1,59 @@
//
// FeatureView.swift
// Pearcleaner
//
// Created by Alin Lupascu on 5/5/24.
//
import SwiftUI
struct FeatureView: View {
@EnvironmentObject var appState: AppState
@AppStorage("settings.general.features") private var features: String = ""
var body: some View {
VStack(spacing: 5) {
HStack {
Spacer()
Text("New features for v\((Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)!)!") .font(.title)
.bold()
.padding(.vertical)
Spacer()
}
Divider()
.padding([.horizontal])
Text(features)
.font(.body)
.lineLimit(3)
.multilineTextAlignment(.leading)
.padding()
Spacer()
HStack(alignment: .center, spacing: 20) {
Button(action: {
NewWin.close()
}) {
Text("Okay")
}
.buttonStyle(SimpleButtonBrightStyle(icon: "checkmark.circle", label: "Ok", help: "Ok", color: .accentColor))
}
.padding(.bottom)
}
.padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0))
}
}
+5 -1
View File
@@ -80,7 +80,11 @@ class NewWin: NSWindowController {
return AnyView(PermView() return AnyView(PermView()
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black.opacity(0.2)) .background(.black.opacity(0.2))
.environmentObject(appState))
case .feature:
return AnyView(FeatureView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black.opacity(0.2))
.environmentObject(appState)) .environmentObject(appState))
} }
} }
+20 -18
View File
@@ -44,7 +44,7 @@ class WindowSettings {
return NSRect(x: x, y: y, width: width, height: height) return NSRect(x: x, y: y, width: width, height: height)
} }
func newWindow<V: View>(withView view: @escaping () -> V) { func newWindow<V: View>(withView view: @escaping () -> V, completion: @escaping () -> Void = {}) {
findAndHideWindows(named: ["Pearcleaner"]) findAndHideWindows(named: ["Pearcleaner"])
let contentView = view let contentView = view
let frame = self.loadWindowSettings() let frame = self.loadWindowSettings()
@@ -56,30 +56,32 @@ class WindowSettings {
newWindow.isMovableByWindowBackground = true newWindow.isMovableByWindowBackground = true
newWindow.center() newWindow.center()
newWindow.title = "Pearcleaner" newWindow.title = "Pearcleaner"
newWindow.isRestorable = false
newWindow.titleVisibility = .hidden newWindow.titleVisibility = .hidden
newWindow.setFrameAutosaveName("Pearcleaner") newWindow.setFrameAutosaveName("Pearcleaner")
newWindow.contentView = NSHostingView(rootView: contentView()) newWindow.contentView = NSHostingView(rootView: contentView())
self.windows.append(newWindow) self.windows.append(newWindow)
newWindow.makeKeyAndOrderFront(nil) newWindow.makeKeyAndOrderFront(nil)
completion()
} }
} }
struct WillRestore: ViewModifier { //struct WillRestore: ViewModifier {
let restore: Bool // let restore: Bool
//
func body(content: Content) -> some View { // func body(content: Content) -> some View {
content // content
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification), perform: { output in // .onReceive(NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification), perform: { output in
let window = output.object as! NSWindow // let window = output.object as! NSWindow
window.isRestorable = false // window.isRestorable = false
}) // })
} // }
} //}
//
extension View { //extension View {
func willRestore(_ restoreState: Bool = true) -> some View { // func willRestore(_ restoreState: Bool = true) -> some View {
modifier(WillRestore(restore: restoreState)) // modifier(WillRestore(restore: restoreState))
} // }
} //}