diff --git a/Pearcleaner/Logic/Logic.swift b/Pearcleaner/Logic/Logic.swift index 8b84d77..f6e5223 100644 --- a/Pearcleaner/Logic/Logic.swift +++ b/Pearcleaner/Logic/Logic.swift @@ -553,7 +553,7 @@ func reversePathsSearch(appState: AppState, locations: Locations, completion: @e let dispatchGroup = DispatchGroup() let allPaths = appState.appInfoStore.flatMap { $0.files.map { $0.path.pearFormat() } } let allNames = appState.appInfoStore.map { $0.appName.pearFormat() } - let skipped = ["apple", "comapple", "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", "sandobxhelper", "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"] // Skip system folders + let skipped = ["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"] // Skip system folders // Skip locations that might not exist diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index d007b4f..e4af7d5 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -75,16 +75,26 @@ struct InfoButton: View { @State private var isPopoverPresented: Bool = false let text: String let color: Color? + let label: String? var body: some View { Button(action: { self.isPopoverPresented.toggle() }) { - Image(systemName: "info.circle.fill") - .resizable() - .scaledToFit() - .frame(width: 14, height: 14) - .foregroundColor(color?.opacity(0.7) ?? Color("mode").opacity(0.7)) + HStack { + Image(systemName: "info.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 14, height: 14) + .foregroundColor(color?.opacity(0.7) ?? Color("mode").opacity(0.7)) + if !label!.isEmpty { + Text(label!) + .font(.callout) + .foregroundColor(color?.opacity(0.7) ?? Color("mode").opacity(0.7)) + + } + } + } .buttonStyle(PlainButtonStyle()) .onHover { isHovered in diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index 33342eb..07f0ff8 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -329,7 +329,7 @@ extension Int { // --- Extend String to remove periods, spaces and lowercase the string extension String { func pearFormat() -> String { - return self.replacingOccurrences(of: ".", with: "").replacingOccurrences(of: " ", with: "").lowercased() + return self.replacingOccurrences(of: ".", with: "").replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "-", with: "").lowercased() } } diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index 0c680b7..c87372e 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -100,6 +100,7 @@ struct PearcleanerApp: App { loadAllPaths(allApps: sortedApps, appState: appState, locations: locations) } + #if !DEBUG Task { diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index cbb7dc0..57783fe 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -72,7 +72,7 @@ struct GeneralSettingsTab: View { .foregroundStyle(.gray) } - InfoButton(text: "When instant search is enabled, all application files are gathered and cached for later use on startup instead of on each app click. There might be a slight delay of a few seconds when launching Pearcleaner depending on the amount of apps you have installed.", color: nil) + InfoButton(text: "When instant search is enabled, all application files are gathered and cached for later use on startup instead of on each app click. There might be a slight delay of a few seconds when launching Pearcleaner depending on the amount of apps you have installed.", color: nil, label: "") Spacer() Toggle(isOn: $instantSearch, label: { @@ -96,7 +96,7 @@ struct GeneralSettingsTab: View { .foregroundStyle(.gray) } - InfoButton(text: "When homebrew cleanup is enabled, Pearcleaner will check if the app you are removing was installed via homebrew and execute a brew uninstall and brew cleanup command as well to let homebrew know that the app is removed. This way your homebrew list will be synced up correctly and caching will be removed.\n\nNOTE: If you undo the file delete with CMD+Z, the files will be put back but homebrew will not be aware of it. To get the homebrew list back in sync you'd need to run:\n brew install APPNAME --force", color: nil) + InfoButton(text: "When homebrew cleanup is enabled, Pearcleaner will check if the app you are removing was installed via homebrew and execute a brew uninstall and brew cleanup command as well to let homebrew know that the app is removed. This way your homebrew list will be synced up correctly and caching will be removed.\n\nNOTE: If you undo the file delete with CMD+Z, the files will be put back but homebrew will not be aware of it. To get the homebrew list back in sync you'd need to run:\n brew install APPNAME --force", color: nil, label: "") Spacer() Toggle(isOn: $brew, label: { @@ -209,7 +209,7 @@ struct GeneralSettingsTab: View { .foregroundStyle(.gray) } - InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.", color: nil) + InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.", color: nil, label: "") Spacer() Toggle(isOn: $miniView, label: { @@ -236,7 +236,7 @@ struct GeneralSettingsTab: View { .foregroundStyle(.gray) } - InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the popover.", color: nil) + InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the popover.", color: nil, label: "") Spacer() Toggle(isOn: $popoverStay, label: { diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index 2b0961b..736e60c 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -77,7 +77,7 @@ struct FilesView: View { Text("•").foregroundStyle(Color("AccentColor")) Text("\(appState.appInfo.appVersion)").font(.title3) if appState.appInfo.appName.count < 5 { - InfoButton(text: "Pearcleaner searches for files via a combination of bundle id and app name. \(appState.appInfo.appName) has a common or short app name so there might be unrelated files found. Please check the list thoroughly before uninstalling.", color: nil) + InfoButton(text: "Pearcleaner searches for files via a combination of bundle id and app name. \(appState.appInfo.appName) has a common or short app name so there might be unrelated files found. Please check the list thoroughly before uninstalling.", color: nil, label: "") } } diff --git a/Pearcleaner/Views/TopBar.swift b/Pearcleaner/Views/TopBar.swift index 423e7e8..392b8ca 100644 --- a/Pearcleaner/Views/TopBar.swift +++ b/Pearcleaner/Views/TopBar.swift @@ -46,6 +46,7 @@ struct TopBar: View { Button("") { withAnimation(.easeInOut(duration: 0.5)) { updateOnMain { + appState.appInfo = .empty if appState.zombieFile.fileSize.keys.count == 0 { appState.currentView = .zombie appState.showProgress.toggle() diff --git a/Pearcleaner/Views/TopBarMini.swift b/Pearcleaner/Views/TopBarMini.swift index 76f61a0..5551d3c 100644 --- a/Pearcleaner/Views/TopBarMini.swift +++ b/Pearcleaner/Views/TopBarMini.swift @@ -71,6 +71,7 @@ struct TopBarMini: View { withAnimation(.easeInOut(duration: 0.5)) { showPopover = false updateOnMain { + appState.appInfo = .empty if appState.zombieFile.fileSize.keys.count == 0 { appState.currentView = .zombie appState.showProgress.toggle() diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index ada94ff..0d7cc8d 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -18,9 +18,36 @@ struct ZombieView: View { @Environment(\.colorScheme) var colorScheme @Binding var showPopover: Bool @Binding var search: String + @State private var searchZ: String = "" @State private var selectedOption = "Default" var body: some View { + +// let filteredAndSortedFiles: [URL] = { +// let files = appState.zombieFile.fileSize.keys.filter { url in +// searchZ.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchZ) +// } +// +// return selectedOption == "Default" ? +// files.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) : +// files.sorted(by: { appState.zombieFile.fileSize[$0, default: 0] > appState.zombieFile.fileSize[$1, default: 0] }) +// }() + let filteredAndSortedFiles: ([URL], Int64) = { + let filteredFiles = appState.zombieFile.fileSize.filter { (url, _) in + searchZ.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchZ) + } + + let sortedFilteredFiles = filteredFiles.sorted(by: { + selectedOption == "Default" ? + $0.key.lastPathComponent < $1.key.lastPathComponent : + $0.value > $1.value + }).map { $0.key } + + let totalSize = filteredFiles.values.reduce(0, +) + + return (sortedFilteredFiles, totalSize) + }() + VStack(alignment: .center) { if appState.showProgress { VStack { @@ -88,7 +115,7 @@ struct ZombieView: View { VStack(alignment: .leading, spacing: 10){ HStack { Text("Leftover Files").font(.title).fontWeight(.bold) - InfoButton(text: "Leftover file search is not 100% accurate as it doesn't have any app bundles to check against. This searches for files/folders and excludes the ones that have overlap with your currently installed apps. Make sure to confirm files marked for removal are correct.", color: .red) + InfoButton(text: "Leftover file search is not 100% accurate as it doesn't have any app bundles to check against. This searches for files/folders and excludes the ones that have overlap with your currently installed apps. Make sure to confirm files marked for removal are correct.", color: .red, label: "READ") Spacer() } @@ -99,8 +126,8 @@ struct ZombieView: View { Spacer() VStack(alignment: .trailing, spacing: 5) { - Text("\(formatByte(size: appState.zombieFile.totalSize))").font(.title).fontWeight(.bold) - Text("\(appState.zombieFile.fileSize.count == 1 ? "\(appState.zombieFile.fileSize.count) item" : "\(appState.zombieFile.fileSize.count) items")").font(.callout).underline().foregroundStyle((.gray.opacity(0.8))) + Text("\(formatByte(size: filteredAndSortedFiles.1))").font(.title).fontWeight(.bold) + Text("\(filteredAndSortedFiles.0.count == 1 ? "\(filteredAndSortedFiles.0.count) item" : "\(filteredAndSortedFiles.0.count) items")").font(.callout).underline().foregroundStyle((.gray.opacity(0.8))) } } @@ -110,19 +137,15 @@ struct ZombieView: View { .padding(.top, 0) } + SearchBarMiniBottom(search: $searchZ) + Divider() .padding() ScrollView() { LazyVStack { - let sortedFilesSize = appState.zombieFile.fileSize.keys.sorted(by: { appState.zombieFile.fileSize[$0, default: 0] > appState.zombieFile.fileSize[$1, default: 0] }) - - let sortedFilesAlpha = appState.zombieFile.fileSize.keys.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) - - let sort = selectedOption == "Default" ? sortedFilesAlpha : sortedFilesSize - - ForEach(sort, id: \.self) { file in + ForEach(filteredAndSortedFiles.0, id: \.self) { file in if let fileSize = appState.zombieFile.fileSize[file], let fileIcon = appState.zombieFile.fileIcon[file] { let iconImage = fileIcon.map(Image.init(nsImage:))