diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index f1be59c..e837724 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -15,7 +15,6 @@ C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71848432B8D2D600046CB13 /* ZombieView.swift */; }; C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; }; C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; }; - C7575D382B0181FC006A600A /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7575D372B0181FC006A600A /* FileWatcher */; }; C7575D392B0182DE006A600A /* PearcleanerSentinel in CopyFiles */ = {isa = PBXBuildFile; fileRef = C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; }; C76D084A2AF83C6E00D07867 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08492AF83C6E00D07867 /* General.swift */; }; @@ -30,6 +29,7 @@ C77B901E2AF1A9DB009CC655 /* SettingsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B901D2AF1A9DB009CC655 /* SettingsWindow.swift */; }; C77B90232AF2D616009CC655 /* FilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90222AF2D616009CC655 /* FilesView.swift */; }; C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */; }; + C7A2AE5D2B9644F300161540 /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7A2AE5C2B9644F300161540 /* FileWatcher */; }; C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A9CE462B89164700EB6E78 /* Authorization.swift */; }; C7ACADE62B92A737000B5845 /* Features.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ACADE52B92A737000B5845 /* Features.swift */; }; C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CF47232B3B3F1700979C5F /* WindowSettings.swift */; }; @@ -37,6 +37,7 @@ C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.swift */; }; C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D502AFF00F300C7ED9E /* Locations.swift */; }; C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; }; + C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7FB173A2B96321300B96F9A /* AppsListView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -95,6 +96,7 @@ C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = ""; }; C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = ""; }; C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + C7FB173A2B96321300B96F9A /* AppsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsListView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -102,7 +104,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C7575D382B0181FC006A600A /* FileWatcher in Frameworks */, + C7A2AE5D2B9644F300161540 /* FileWatcher in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -146,7 +148,6 @@ C72893102AFD51EA00C8C1CD /* PearcleanerSentinel */ = { isa = PBXGroup; children = ( - C7A27E822AFD996100166168 /* Resources */, C7A27E7F2AFD70DD00166168 /* PearcleanerSentinel.entitlements */, C72893112AFD51EA00C8C1CD /* main.swift */, ); @@ -224,17 +225,11 @@ C7D31D492AFEB26700C7ED9E /* AppListItems.swift */, C7045A272B03E71D00376976 /* MiniMode.swift */, C71848432B8D2D600046CB13 /* ZombieView.swift */, + C7FB173A2B96321300B96F9A /* AppsListView.swift */, ); path = Views; sourceTree = ""; }; - C7A27E822AFD996100166168 /* Resources */ = { - isa = PBXGroup; - children = ( - ); - path = Resources; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -252,7 +247,7 @@ ); name = PearcleanerSentinel; packageProductDependencies = ( - C7575D372B0181FC006A600A /* FileWatcher */, + C7A2AE5C2B9644F300161540 /* FileWatcher */, ); productName = PeakPurgeMonitor; productReference = C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */; @@ -306,7 +301,7 @@ ); mainGroup = C77B8FF72AF18E2E009CC655; packageReferences = ( - C7575D362B0181F3006A600A /* XCRemoteSwiftPackageReference "FileWatcher" */, + C7A2AE5B2B9644DA00161540 /* XCRemoteSwiftPackageReference "FileWatcher" */, ); productRefGroup = C77B90012AF18E2E009CC655 /* Products */; projectDirPath = ""; @@ -344,6 +339,7 @@ buildActionMask = 2147483647; files = ( C76D08482AF83C3F00D07867 /* Update.swift in Sources */, + C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */, C7045A342B068B2900376976 /* UpdateView.swift in Sources */, C77B90162AF19377009CC655 /* AppState.swift in Sources */, C7045A322B068AD700376976 /* NewWin.swift in Sources */, @@ -383,6 +379,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = BK8443AXLU; ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 13.0; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.PearcleanerSentinel; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -400,6 +397,7 @@ DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = BK8443AXLU; ENABLE_HARDENED_RUNTIME = YES; + FRAMEWORK_SEARCH_PATHS = ""; MACOSX_DEPLOYMENT_TARGET = 13.0; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.PearcleanerSentinel; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -448,6 +446,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; + FRAMEWORK_SEARCH_PATHS = ""; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -513,6 +512,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; + FRAMEWORK_SEARCH_PATHS = ""; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -539,7 +539,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -557,7 +557,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.0.1; + MARKETING_VERSION = 3.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -574,7 +574,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -592,7 +592,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.0.1; + MARKETING_VERSION = 3.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -633,7 +633,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - C7575D362B0181F3006A600A /* XCRemoteSwiftPackageReference "FileWatcher" */ = { + C7A2AE5B2B9644DA00161540 /* XCRemoteSwiftPackageReference "FileWatcher" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/eonist/FileWatcher.git"; requirement = { @@ -644,9 +644,9 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - C7575D372B0181FC006A600A /* FileWatcher */ = { + C7A2AE5C2B9644F300161540 /* FileWatcher */ = { isa = XCSwiftPackageProductDependency; - package = C7575D362B0181F3006A600A /* XCRemoteSwiftPackageReference "FileWatcher" */; + package = C7A2AE5B2B9644DA00161540 /* XCRemoteSwiftPackageReference "FileWatcher" */; productName = FileWatcher; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Pearcleaner/Logic/AppCommands.swift b/Pearcleaner/Logic/AppCommands.swift index 2e30ee3..a2c873a 100644 --- a/Pearcleaner/Logic/AppCommands.swift +++ b/Pearcleaner/Logic/AppCommands.swift @@ -8,7 +8,8 @@ import SwiftUI struct AppCommands: Commands { - + @AppStorage("settings.general.instant") private var instantSearch: Bool = true + let appState: AppState let locations: Locations @@ -32,18 +33,20 @@ struct AppCommands: Commands { Button { withAnimation(.easeInOut(duration: 0.5)) { // Refresh Apps list - appState.reload.toggle() - let sortedApps = getSortedApps() updateOnMain { + appState.reload.toggle() + } + let sortedApps = getSortedApps() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { appState.sortedApps.userApps = [] appState.sortedApps.systemApps = [] appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps.systemApps = sortedApps.systemApps - } - Task(priority: .high){ - loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) - } - updateOnMain { + if instantSearch { + Task(priority: .high){ + loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) + } + } appState.reload.toggle() } } @@ -64,16 +67,18 @@ struct AppCommands: Commands { // Edit Menu CommandGroup(replacing: .undoRedo) { + Button { undoTrash(appState: appState) { let sortedApps = getSortedApps() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - appState.sortedApps.userApps = [] - appState.sortedApps.systemApps = [] + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps.systemApps = sortedApps.systemApps - loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) +// if instantSearch { +// loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) +// } + } } } label: { diff --git a/Pearcleaner/Logic/Logic.swift b/Pearcleaner/Logic/Logic.swift index 2e59b8c..18bcd20 100644 --- a/Pearcleaner/Logic/Logic.swift +++ b/Pearcleaner/Logic/Logic.swift @@ -321,7 +321,14 @@ func findPathsForApp(appInfo: AppInfo = .empty, appState: AppState, locations: L var collection: [URL] = [] if let url = URL(string: appInfo.path.absoluteString) { - collection.insert(url, at: 0) + if !url.path.contains(".Trash") { + if url.path.contains("Wrapper") { + let modifiedUrl = url.deletingLastPathComponent().deletingLastPathComponent() + collection.insert(modifiedUrl, at: 0) + } else { + collection.insert(url, at: 0) + } + } } @@ -329,7 +336,6 @@ func findPathsForApp(appInfo: AppInfo = .empty, appState: AppState, locations: L let dispatchGroup = DispatchGroup() var bundleComponents = appInfo.bundleIdentifier.components(separatedBy: ".") if let lastComponent = bundleComponents.last, let rangeOfDash = lastComponent.range(of: "-") { - // Remove the dash and everything after it let updatedLastComponent = String(lastComponent[.. Void = {}) } } } - + if !filesSudo.isEmpty { // Remove socket files with rm let filesSudoPaths = filesSudo.map { $0.path } diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index ee19704..21e8133 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -504,6 +504,40 @@ struct WindowActionButton: ButtonStyle { } +struct SimpleButtonBrightStyle: ButtonStyle { + @State private var hovered = false + let icon: String + let help: String + let color: Color + let shield: Bool? + + init(icon: String, help: String, color: Color, shield: Bool? = nil) { + self.icon = icon + self.help = help + self.color = color + self.shield = shield + } + + func makeBody(configuration: Self.Configuration) -> some View { + HStack { + Image(systemName: icon) + .resizable() + .scaledToFit() + .frame(width: 20) + .foregroundColor(hovered ? color.opacity(0.5) : color) + } + .padding(5) + .onHover { hovering in + withAnimation() { + hovered = hovering + } + } + .scaleEffect(configuration.isPressed ? 0.95 : 1) + .help(help) + } +} + + struct FilesViewActionButton: ButtonStyle { enum UserAction { diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index 1c45e1c..4e255d2 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -427,7 +427,7 @@ func ensureApplicationSupportFolderExists(appState: AppState) { let fileManager = FileManager.default let supportURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("Pearcleaner") - // Check to make sure Application Support/Support Admin folder exists + // Check to make sure Application Support/Pearcleaner folder exists if !fileManager.fileExists(atPath: supportURL.path) { try! fileManager.createDirectory(at: supportURL, withIntermediateDirectories: true) printOS("Created Application Support/Pearcleaner folder") @@ -463,7 +463,7 @@ func writeLog(string: String) { // --- Load Plist file with launchctl --- -func launchctl(load: Bool) { +func launchctl(load: Bool, completion: @escaping () -> Void = {}) { let cmd = load ? "load" : "unload" if let plistPath = Bundle.main.path(forResource: "com.alienator88.PearcleanerSentinel", ofType: "plist") { var plistContent = try! String(contentsOfFile: plistPath) @@ -491,6 +491,8 @@ func launchctl(load: Bool) { task.launch() + completion() + } } diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index a8420ef..14daf02 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -29,8 +29,6 @@ struct PearcleanerApp: App { var body: some Scene { - - WindowGroup { Group { ZStack() { @@ -53,7 +51,7 @@ struct PearcleanerApp: App { } .environmentObject(appState) .preferredColorScheme(displayMode.colorScheme) - .alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) } +// .alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) } .handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*")) .onOpenURL(perform: { url in let deeplinkManager = DeeplinkManager(showPopover: $showPopover) @@ -101,8 +99,7 @@ struct PearcleanerApp: App { if instantSearch { loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) } - - + #if !DEBUG Task { diff --git a/Pearcleaner/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Pearcleaner/Resources/Assets.xcassets/AccentColor.colorset/Contents.json index 92ef031..8e9a1c7 100644 --- a/Pearcleaner/Resources/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Pearcleaner/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "display-p3", "components" : { "alpha" : "1.000", - "blue" : "0xFF", - "green" : "0xFF", - "red" : "0xFF" + "blue" : "0x50", + "green" : "0xC3", + "red" : "0x79" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "display-p3", "components" : { "alpha" : "1.000", - "blue" : "0xFF", - "green" : "0xFF", - "red" : "0xFF" + "blue" : "0x50", + "green" : "0xC3", + "red" : "0x79" } }, "idiom" : "universal" diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index d87e744..e43d4a5 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -48,7 +48,7 @@ struct GeneralSettingsTab: View { .font(.callout) .foregroundStyle(.gray) } - InfoButton(text: "When transparent material is enabled, sticky section headers (User/System) in app list are disabled to keep from showing app name text overlayed under the section header text with no background to separate the two.", color: nil) +// InfoButton(text: "When transparent material is enabled, sticky section headers (User/System) in app list are disabled to keep from showing app name text overlayed under the section header text with no background to separate the two.", color: nil) Spacer() Toggle(isOn: $glass, label: { }) diff --git a/Pearcleaner/Settings/Update.swift b/Pearcleaner/Settings/Update.swift index 8d43dc0..b329500 100644 --- a/Pearcleaner/Settings/Update.swift +++ b/Pearcleaner/Settings/Update.swift @@ -24,7 +24,7 @@ struct Asset: Codable { extension Release { var modifiedBody: String { - return body.replacingOccurrences(of: "- [x]", with: "􀆊").replacingOccurrences(of: "###", with: "") + return body.replacingOccurrences(of: "- [x]", with: ">").replacingOccurrences(of: "###", with: "") } } diff --git a/Pearcleaner/Views/AppListView.swift b/Pearcleaner/Views/AppListView.swift index 31d5cc2..4146108 100644 --- a/Pearcleaner/Views/AppListView.swift +++ b/Pearcleaner/Views/AppListView.swift @@ -64,53 +64,9 @@ struct AppListView: View { } } .padding(.top, 20) -// .padding(.bottom) - ScrollView { - LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [glass ? [] : .sectionHeaders]) { - - if filteredUserApps.count > 0 { -// VStack { -// Header(title: "User", count: filteredUserApps.count, showPopover: $showPopover) - ForEach(filteredUserApps, id: \.self) { appInfo in - Section(header: Header(title: "User", count: filteredUserApps.count, showPopover: $showPopover)) { - AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) - .padding(.vertical,5) - if appInfo != filteredUserApps.last { - Divider().padding(.horizontal, 5) - } - } -// AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) -// if appInfo != filteredUserApps.last { -// Divider().padding(.horizontal, 5) -// } - } -// } - - } - - if filteredSystemApps.count > 0 { -// VStack { -// Header(title: "System", count: filteredSystemApps.count, showPopover: $showPopover) - Section(header: Header(title: "System", count: filteredSystemApps.count, showPopover: $showPopover)) { - - ForEach(filteredSystemApps, id: \.self) { appInfo in - AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) - .padding(.vertical,5) - if appInfo != filteredSystemApps.last { - Divider().padding(.horizontal, 5) - } - } - } -// } - } - - } - .padding(.horizontal) - - } - .scrollIndicators(.never) + AppsListView(search: $search, showPopover: $showPopover, filteredUserApps: filteredUserApps, filteredSystemApps: filteredSystemApps) } .frame(width: sidebarWidth) @@ -267,6 +223,6 @@ struct Header: View { } .padding(.vertical, 9) .padding(.leading, 6) - .background(!glass ? Color("windowBG") : .clear) +// .background(!glass ? Color("windowBG") : .clear) } } diff --git a/Pearcleaner/Views/AppsListView.swift b/Pearcleaner/Views/AppsListView.swift new file mode 100644 index 0000000..6e15b87 --- /dev/null +++ b/Pearcleaner/Views/AppsListView.swift @@ -0,0 +1,79 @@ +// +// AppsListView.swift +// Pearcleaner +// +// Created by Alin Lupascu on 3/4/24. +// + +import Foundation +import SwiftUI + +struct AppsListView: View { + @Binding var search: String + @Binding var showPopover: Bool + var filteredUserApps: [AppInfo] + var filteredSystemApps: [AppInfo] + + var body: some View { + ScrollView { + LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) { + if filteredUserApps.count > 0 { + SectionView(title: "User", count: filteredUserApps.count, apps: filteredUserApps, search: $search, showPopover: $showPopover) + } + + if filteredSystemApps.count > 0 { + SectionView(title: "System", count: filteredSystemApps.count, apps: filteredSystemApps, search: $search, showPopover: $showPopover) + } + } + .padding(.horizontal) + } + .scrollIndicators(.never) + } +} + +struct SectionView: View { + var title: String + var count: Int + var apps: [AppInfo] + @Binding var search: String + @Binding var showPopover: Bool + + var body: some View { + VStack(spacing: 0) { + Header(title: title, count: count, showPopover: $showPopover) + ForEach(apps, id: \.self) { appInfo in + AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) + .padding(.vertical, 5) + if appInfo != apps.last { + Divider().padding(.horizontal, 5) + } + } + } + } +} + + +//List { +// Section { +// ForEach(filteredUserApps, id:\.self) { element in +// Text(element.appName) +// } +// .listRowBackground(Color.clear) +// } header: { +// Text("User Apps") +// } +// +// Section { +// ForEach(filteredSystemApps, id:\.self) { element in +// Text(element.appName) +// } +// .listRowBackground(Color.clear) +// } header: { +// Text("System Apps") +// } +// +//} +//.scrollContentBackground(.hidden) +//.background(.clear) +//.scrollIndicators(.never) + diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index bd20307..46ff7e7 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -14,6 +14,7 @@ struct FilesView: View { @State private var showPop: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false + @AppStorage("settings.general.instant") var instantSearch: Bool = true @Environment(\.colorScheme) var colorScheme @Binding var showPopover: Bool @Binding var search: String @@ -35,8 +36,24 @@ struct FilesView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .transition(.opacity) } else { - VStack() { + // Titlebar + if mini { + HStack() { + Spacer() + Button("Close") { + updateOnMain { + appState.appInfo = AppInfo.empty + search = "" + appState.currentView = .apps + showPopover = false + } + } + .buttonStyle(NavButtonBottomBarStyle(image: "x.circle.fill", help: "Close")) + } + .padding([.horizontal, .top], 5) + } + VStack(spacing: 0) { // Main Group HStack(alignment: .center) { @@ -112,7 +129,7 @@ struct FilesView: View { } .padding(.horizontal, 20) - .padding(.top, 10) + .padding(.top, 0) } Divider() @@ -153,8 +170,8 @@ struct FilesView: View { } } )) { - Text("􀃲").tag(true) - Text("􀂒").tag(false) + Image(systemName: "checkmark.square").tag(true) + Image(systemName: "square").tag(false) } .pickerStyle(SegmentedPickerStyle()) .frame(width: 100) @@ -163,20 +180,6 @@ struct FilesView: View { Spacer() - if mini { - - Button("Close") { - updateOnMain { - appState.appInfo = AppInfo.empty - search = "" - appState.currentView = .apps - showPopover = false - } - } - .buttonStyle(NavButtonBottomBarStyle(image: "x.circle.fill", help: "Close")) - - } - if !appState.selectedItems.isEmpty { Button("Uninstall") { Task { @@ -191,13 +194,9 @@ struct FilesView: View { } } var selectedItemsArray = Array(appState.selectedItems) - .filter { !$0.path.contains(".Trash") } - .map { path in - return path.path.contains("Wrapper") ? path.deletingLastPathComponent().deletingLastPathComponent() : path - } if let url = URL(string: appState.appInfo.path.absoluteString) { - let appFolderURL = url.deletingLastPathComponent() // Get the immediate parent directory + let appFolderURL = appState.appInfo.path.absoluteString.contains("Wrapper") ? url.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() : url.deletingLastPathComponent() // Get the immediate parent directory of regular and wrapped apps if appFolderURL.path == "/Applications" || appFolderURL.path == "\(home)/Applications" { // Do nothing, skip insertion @@ -207,7 +206,6 @@ struct FilesView: View { } } - killApp(appId: appState.appInfo.bundleIdentifier) { moveFilesToTrash(at: selectedItemsArray) { withAnimation { @@ -226,7 +224,7 @@ struct FilesView: View { .buttonStyle(NavButtonBottomBarStyle(image: "trash.fill", help: "Uninstall")) } else { - Text("No files selected to clean").font(.title).foregroundStyle(Color("mode")).opacity(0.2) + Text("No files selected to remove").font(.title).foregroundStyle(Color("mode")).opacity(0.2) .padding(5) } @@ -234,17 +232,17 @@ struct FilesView: View { Spacer() Picker("", selection: $selectedOption) { - Text("􀅐").tag("Default") - Text("􀆃").tag("Size") + Image(systemName: "textformat.abc").tag("Default") + Image(systemName: "number").tag("Size") } .pickerStyle(SegmentedPickerStyle()) .frame(width: 100) - .help("Sorting Selection") + .help("Sorting alphabetically or by size") } } .transition(.opacity) - .padding(20) + .padding([.horizontal, .bottom], 20) } @@ -254,14 +252,12 @@ struct FilesView: View { func refreshAppList(_ appInfo: AppInfo) { showPopover = false let sortedApps = getSortedApps() - updateOnMain { - appState.sortedApps.userApps = [] - appState.sortedApps.systemApps = [] + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps.systemApps = sortedApps.systemApps - } - Task(priority: .high){ - loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) +// if instantSearch { +// loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) +// } } } } diff --git a/Pearcleaner/Views/MiniMode.swift b/Pearcleaner/Views/MiniMode.swift index a1643a6..d992684 100644 --- a/Pearcleaner/Views/MiniMode.swift +++ b/Pearcleaner/Views/MiniMode.swift @@ -186,49 +186,7 @@ struct MiniAppView: View { } else { VStack(alignment: .center) { - ScrollView { - - LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [glass ? [] : .sectionHeaders]) { - - if filteredUserApps.count > 0 { - -// VStack { -// Header(title: "User", count: filteredUserApps.count, showPopover: $showPopover) - Section(header: Header(title: "User", count: filteredUserApps.count, showPopover: $showPopover)) { - - ForEach(filteredUserApps, id: \.self) { appInfo in - AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) -// .padding(.vertical,5) - if appInfo != filteredUserApps.last { - Divider().padding(.horizontal, 5) - } - } - } -// } - - } - - if filteredSystemApps.count > 0 { - -// VStack { -// Header(title: "System", count: filteredSystemApps.count, showPopover: $showPopover) - Section(header: Header(title: "System", count: filteredSystemApps.count, showPopover: $showPopover)) { - ForEach(filteredSystemApps, id: \.self) { appInfo in - AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) - .padding(.vertical,5) - if appInfo != filteredSystemApps.last { - Divider().padding(.horizontal, 5) - } - } - } -// } - } - - } - .padding(.horizontal) - - } - .scrollIndicators(.never) + AppsListView(search: $search, showPopover: $showPopover, filteredUserApps: filteredUserApps, filteredSystemApps: filteredSystemApps) if appState.currentView != .empty { SearchBarMiniBottom(search: $search).background(.ultraThinMaterial) diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index 4709dec..9ca1c71 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -37,8 +37,38 @@ struct ZombieView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .transition(.opacity) } else { - VStack() { + // Titlebar + if mini { + HStack(spacing: 0) { + Spacer() + Button("Rescan") { + updateOnMain { + appState.zombieFile = .empty + appState.showProgress.toggle() + if instantSearch { + reversePathsSearch(appState: appState, locations: locations) + } else { + loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true) + } + } + } + .buttonStyle(NavButtonBottomBarStyle(image: "arrow.counterclockwise.circle.fill", help: "Rescan files")) + + Button("Close") { + updateOnMain { + appState.appInfo = AppInfo.empty + search = "" + appState.currentView = .apps + showPopover = false + } + } + .buttonStyle(NavButtonBottomBarStyle(image: "x.circle.fill", help: "Close")) + } + .padding([.horizontal, .top], 5) + } + + VStack() { // Main Group HStack() { @@ -74,7 +104,7 @@ struct ZombieView: View { } .padding(.horizontal, 20) - .padding(.top, 10) + .padding(.top, 0) } Divider() @@ -116,8 +146,8 @@ struct ZombieView: View { } } )) { - Text("􀃲").tag(true) - Text("􀂒").tag(false) + Image(systemName: "checkmark.square").tag(true) + Image(systemName: "square").tag(false) } .pickerStyle(SegmentedPickerStyle()) .frame(width: 100) @@ -126,23 +156,6 @@ struct ZombieView: View { Spacer() - if mini { - Button("Close") { - updateOnMain { - appState.appInfo = AppInfo.empty - search = "" - appState.currentView = .apps - showPopover = false - } - } - .buttonStyle(NavButtonBottomBarStyle(image: "x.circle.fill", help: "Close")) - - } - - - - - if !appState.selectedZombieItems.isEmpty { Button("Remove") { Task { @@ -158,7 +171,6 @@ struct ZombieView: View { } let selectedItemsArray = Array(appState.selectedZombieItems) - .filter { !$0.path.contains(".Trash") } killApp(appId: appState.appInfo.bundleIdentifier) { moveFilesToTrash(at: selectedItemsArray) { @@ -166,9 +178,6 @@ struct ZombieView: View { showPopover = false updateOnMain { appState.isReminderVisible.toggle() - if sentinel { - launchctl(load: true) - } } } } @@ -183,53 +192,25 @@ struct ZombieView: View { .padding(5) } - Button("Rescan") { - updateOnMain { - appState.zombieFile = .empty - appState.showProgress.toggle() - if instantSearch { - reversePathsSearch(appState: appState, locations: locations) - } else { - loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true) - } - } - } - .buttonStyle(NavButtonBottomBarStyle(image: "arrow.counterclockwise.circle.fill", help: "Rescan files")) - - Spacer() Picker("", selection: $selectedOption) { - Text("􀅐").tag("Default") - Text("􀆃").tag("Size") + Image(systemName: "textformat.abc").tag("Default") + Image(systemName: "number").tag("Size") } .pickerStyle(SegmentedPickerStyle()) .frame(width: 100) - .help("Sorting Selection") + .help("Sorting alphabetically or by size") } } .transition(.opacity) - .padding(20) + .padding([.horizontal, .bottom], 20) } } } - - func refreshAppList(_ appInfo: AppInfo) { - showPopover = false - let sortedApps = getSortedApps() - updateOnMain { - appState.sortedApps.userApps = [] - appState.sortedApps.systemApps = [] - appState.sortedApps.userApps = sortedApps.userApps - appState.sortedApps.systemApps = sortedApps.systemApps - } - Task(priority: .high){ - loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) - } - } } diff --git a/Pearcleaner/Windows/NewWin.swift b/Pearcleaner/Windows/NewWin.swift index 820a3be..f2e2c81 100644 --- a/Pearcleaner/Windows/NewWin.swift +++ b/Pearcleaner/Windows/NewWin.swift @@ -27,7 +27,7 @@ class NewWin: NSWindowController { let window = Self.makeWindow(width: width, height: height) window.backgroundColor = NSColor.controlBackgroundColor - + self.init(window: window) // Using Visual Effect to make titlebar fully transparent @@ -47,7 +47,7 @@ class NewWin: NSWindowController { window.titleVisibility = .hidden window.titlebarAppearsTransparent = true window.center() - window.title = "Support Admin" + window.title = "Pearcleaner" } @@ -65,10 +65,12 @@ class NewWin: NSWindowController { } private func makeNewView(appState: AppState, newWin: NewWindow) -> some View { + switch newWin { case .update: return AnyView(UpdateView() .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(.black.opacity(0.2)) .environmentObject(appState)) case .no_update: return AnyView(NoUpdateView() @@ -77,6 +79,8 @@ class NewWin: NSWindowController { case .perm: return AnyView(PermView() .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(.black.opacity(0.2)) + .environmentObject(appState)) } } diff --git a/Pearcleaner/Windows/PermView.swift b/Pearcleaner/Windows/PermView.swift index f31e544..3a2d85d 100644 --- a/Pearcleaner/Windows/PermView.swift +++ b/Pearcleaner/Windows/PermView.swift @@ -17,34 +17,69 @@ struct PermView: View { Spacer() - Text("Attention") + Text("Pearcleaner Permissions") .font(.title) .bold() .padding(.vertical) - + .padding(.top, 5) + Spacer() } - .background(.red) - - Text("Pearcleaner requires the following permissions:") - .font(.title3) - .fontWeight(.semibold) - .padding(.vertical) - ScrollView { - Text(" - Full Disk Access permission to find and delete files in non-user locations.\n - Accessibility permission to delete files via Finder which allows for the Undo function.\n\nAdd Pearcleaner in both Privacy panes via the + or by dragging the app over the pane. If the app is already pre-populated in the list, just toggle On. Restart app when both permissions are granted.") - .font(.body) - .multilineTextAlignment(.leading) - .padding(.horizontal, 20) + Divider() + .padding(.bottom) + + Spacer() + + VStack(alignment: .leading, spacing: 20) { + HStack(alignment: .top, spacing: 20) { + Image(systemName: "externaldrive") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundStyle(.gray) + Text("Full Disk Access permission to find and delete files in system paths") + .font(.callout) + .foregroundStyle(.gray) + } + + HStack(alignment: .top, spacing: 20) { + Image(systemName: "accessibility") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundStyle(.gray) + Text("Accessibility permission to delete files via Finder") + .font(.callout) + .foregroundStyle(.gray) + } + + HStack(alignment: .top, spacing: 20) { + Image(systemName: "info.circle") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .foregroundStyle(.gray) + Text("Add Pearcleaner in both Privacy panes via the + or by dragging the app over the pane. If the app is already pre-populated in the list, just toggle On if neeeded. Restart app when both permissions are granted") + .font(.callout) + .foregroundStyle(.gray) + } } - .frame(height: 130) - .padding(.horizontal) + .padding(.horizontal, 20) Spacer() HStack { + Button(action: { + relaunchApp(afterDelay: 1) + }) { + Text("Restart") + } + .buttonStyle(SimpleButtonBrightStyle(icon: "arrow.uturn.left.circle", help: "Restart", color: .red)) + .padding() + Button(action: { if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") { NSWorkspace.shared.open(url) @@ -52,7 +87,8 @@ struct PermView: View { }) { Text("Full Disk Access") } - .buttonStyle(WindowActionButton(action: .accept)) + .buttonStyle(SimpleButtonBrightStyle(icon: "externaldrive", help: "Full Disk", color: .accentColor)) + .padding() Button(action: { @@ -62,16 +98,10 @@ struct PermView: View { }) { Text("Accessibility") } - .buttonStyle(WindowActionButton(action: .accept)) + .buttonStyle(SimpleButtonBrightStyle(icon: "accessibility", help: "Accessibility", color: .accentColor)) .padding() - Button(action: { - relaunchApp(afterDelay: 1) - }) { - Text("Restart") - } - .buttonStyle(WindowActionButton(action: .cancel)) - .padding() + } diff --git a/Pearcleaner/Windows/UpdateView.swift b/Pearcleaner/Windows/UpdateView.swift index c85dd85..ca664de 100644 --- a/Pearcleaner/Windows/UpdateView.swift +++ b/Pearcleaner/Windows/UpdateView.swift @@ -13,7 +13,7 @@ struct UpdateView: View { let isAppInAppsDir = isAppInApplicationsDir() var body: some View { - VStack { + VStack(spacing: 5) { HStack { Spacer() @@ -29,36 +29,28 @@ struct UpdateView: View { Text("v\(appState.releases.first?.tag_name ?? "")") .font(.title3) .fontWeight(.semibold) - .foregroundStyle(Color.white.opacity(0.5)) + .opacity(0.5) .padding() } - .background(.blue) - - Spacer() - + + Divider() + .padding([.horizontal]) + ScrollView { - Text("\(appState.releases.first?.body ?? "No release information")") + Text("\(appState.releases.first?.modifiedBody ?? "No release information")") .font(.body) .multilineTextAlignment(.leading) - .padding(.horizontal, 20) + .padding(20) } - .frame(height: 160) - .padding() Spacer() if !isAppInAppsDir { - HStack { - Text("Pearcleaner not in /Applications directory. Updater could have permission issues updating files.") - .font(.footnote) - .foregroundStyle(.red) - .padding() - .lineLimit(2) - .fixedSize(horizontal: false, vertical: true) - } - - + Text("Please move Pearcleaner to the **Applications** folder before updating!") + .font(.callout) + .foregroundStyle(.red) + .padding(.top, 0) } VStack() { @@ -73,7 +65,7 @@ struct UpdateView: View { Button(action: NewWin.close) { Text("Cancel") } - .buttonStyle(WindowActionButton(action: .cancel)) + .buttonStyle(SimpleButtonBrightStyle(icon: "x.circle", help: "Close", color: .red)) if appState.progressBar.1 != 1.0 { Button(action: { @@ -81,7 +73,7 @@ struct UpdateView: View { }) { Text("Update") } - .buttonStyle(WindowActionButton(action: .accept)) + .buttonStyle(SimpleButtonBrightStyle(icon: "arrow.down.circle", help: "Update", color: .accentColor)) } else { Button(action: { NewWin.close() @@ -89,7 +81,7 @@ struct UpdateView: View { }) { Text("Restart") } - .buttonStyle(WindowActionButton(action: .accept)) + .buttonStyle(SimpleButtonBrightStyle(icon: "arrow.uturn.left.circle", help: "Restart", color: .accentColor)) } } @@ -99,7 +91,7 @@ struct UpdateView: View { } .padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0)) - + } @@ -110,9 +102,9 @@ struct UpdateView: View { struct NoUpdateView: View { @EnvironmentObject var appState: AppState - + var body: some View { - VStack { + VStack(spacing: 5) { HStack { Spacer() @@ -128,19 +120,19 @@ struct NoUpdateView: View { Text("v\(appState.releases.first?.tag_name ?? "")") .font(.title3) .fontWeight(.semibold) - .foregroundStyle(Color.white.opacity(0.5)) + .opacity(0.5) .padding() } - .background(.blue) - - Spacer() + + Divider() + .padding([.horizontal]) Text("Pearcleaner is on the most current release available, but you may force a re-download of the same version below.") .font(.body) .lineLimit(3) .multilineTextAlignment(.leading) - .padding(20) + .padding() @@ -160,15 +152,15 @@ struct NoUpdateView: View { }) { Text("Force Update") } - .buttonStyle(WindowActionButton(action: .cancel)) - + .buttonStyle(SimpleButtonBrightStyle(icon: "arrow.down.circle", help: "Force-Update", color: .red)) + Button(action: { NewWin.close() }) { Text("Okay") } - .buttonStyle(WindowActionButton(action: .accept)) - + .buttonStyle(SimpleButtonBrightStyle(icon: "checkmark.circle", help: "Ok", color: .accentColor)) + } @@ -179,7 +171,7 @@ struct NoUpdateView: View { }) { Text("Restart") } - .buttonStyle(WindowActionButton(action: .accept)) + .buttonStyle(SimpleButtonBrightStyle(icon: "arrow.uturn.left.circle", help: "Restart", color: .accentColor)) } } .padding(.bottom) @@ -190,7 +182,6 @@ struct NoUpdateView: View { .padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0)) - } diff --git a/PearcleanerSentinel/main.swift b/PearcleanerSentinel/main.swift index c50f874..cfb2d06 100644 --- a/PearcleanerSentinel/main.swift +++ b/PearcleanerSentinel/main.swift @@ -6,8 +6,8 @@ // import Foundation -import FileWatcher import AppKit +import FileWatcher // Start Trash monitoring fileWatcher() @@ -31,8 +31,16 @@ func checkApp(file: String) { let app = URL(fileURLWithPath: file) let appExt = app.pathExtension if appExt == "app" { - if FileManager.default.isInTrash(app) { - NSWorkspace.shared.open(URL(string: "pear://com.alienator88.Pearcleaner?path=\(file)")!) + if let appBundle = Bundle(url: app) { + if appBundle.bundleIdentifier == "com.alienator88.Pearcleaner" { + return + } else { + if FileManager.default.isInTrash(app) { + NSWorkspace.shared.open(URL(string: "pear://com.alienator88.Pearcleaner?path=\(file)")!) + } + } + } else { + print("Error: Unable to get bundle information for \(file)") } } }