From 7906cd70f719b2a06da9959fc36f0c0d7a6d6664 Mon Sep 17 00:00:00 2001 From: Alin Date: Fri, 22 Mar 2024 18:16:55 -0600 Subject: [PATCH] v3.3.1 --- Pearcleaner.xcodeproj/project.pbxproj | 10 +- Pearcleaner/Logic/AppCommands.swift | 23 ++- Pearcleaner/Logic/AppPathsFetch.swift | 3 +- Pearcleaner/Logic/AppState.swift | 1 + Pearcleaner/Logic/ReversePathsFetch.swift | 4 +- Pearcleaner/Logic/Styles.swift | 4 +- Pearcleaner/Logic/Utilities.swift | 44 ++++- Pearcleaner/PearcleanerApp.swift | 39 ++-- Pearcleaner/Views/FilesView.swift | 198 +++++++++++++-------- Pearcleaner/Views/MenuBarMiniAppView.swift | 31 ---- Pearcleaner/Views/MiniMode.swift | 15 +- Pearcleaner/Views/ZombieView.swift | 107 +++++++---- Pearcleaner/Windows/UpdateView.swift | 18 +- 13 files changed, 311 insertions(+), 186 deletions(-) diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index 6b4f6e1..428b032 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -245,12 +245,12 @@ isa = PBXGroup; children = ( C77B90222AF2D616009CC655 /* FilesView.swift */, + C71848432B8D2D600046CB13 /* ZombieView.swift */, C76D08542AF89CDE00D07867 /* RegularMode.swift */, C7D31D472AFEB23700C7ED9E /* TopBar.swift */, C7045A292B03FAF000376976 /* TopBarMini.swift */, C7D31D492AFEB26700C7ED9E /* AppListItems.swift */, C7045A272B03E71D00376976 /* MiniMode.swift */, - C71848432B8D2D600046CB13 /* ZombieView.swift */, C7FB173A2B96321300B96F9A /* AppsListView.swift */, C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */, ); @@ -575,7 +575,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 26; + CURRENT_PROJECT_VERSION = 27; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -593,7 +593,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.3.0; + MARKETING_VERSION = 3.3.1; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -610,7 +610,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 26; + CURRENT_PROJECT_VERSION = 27; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -628,7 +628,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.3.0; + MARKETING_VERSION = 3.3.1; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Pearcleaner/Logic/AppCommands.swift b/Pearcleaner/Logic/AppCommands.swift index 3ad4720..711c122 100644 --- a/Pearcleaner/Logic/AppCommands.swift +++ b/Pearcleaner/Logic/AppCommands.swift @@ -64,7 +64,7 @@ struct AppCommands: Commands { } } - + // Edit Menu @@ -74,12 +74,12 @@ struct AppCommands: Commands { { undoTrash(appState: appState) { let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState) - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { appState.sortedApps = sortedApps -// if instantSearch { -// loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) -// } - + for app in appState.trashedFiles { + let pathFinder = AppPathFinder(appInfo: app, appState: appState, locations: locations) + pathFinder.findPaths() + } } } } label: { @@ -88,6 +88,17 @@ struct AppCommands: Commands { .keyboardShortcut("z", modifiers: .command) +// Button +// { +// updateOnMain { +// appState.currentView = .zombie +// } +// } label: { +// Label("Zombie", systemImage: "clear") +// } +// .keyboardShortcut("f", modifiers: .command) + + } diff --git a/Pearcleaner/Logic/AppPathsFetch.swift b/Pearcleaner/Logic/AppPathsFetch.swift index fef0e65..ae082b9 100644 --- a/Pearcleaner/Logic/AppPathsFetch.swift +++ b/Pearcleaner/Logic/AppPathsFetch.swift @@ -210,9 +210,10 @@ class AppPathFinder { self.appState.appInfo = self.appInfo self.appState.selectedItems = Set(updatedCollection) } - self.appState.appInfoStore.append(self.appInfo) + // Only append object to store if instant search. Same for calculating progress. if self.instantSearch { + self.appState.appInfoStore.append(self.appInfo) self.appState.instantProgress += 1 } diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index da09df7..ff17f81 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -14,6 +14,7 @@ class AppState: ObservableObject { @Published var appInfo: AppInfo @Published var appInfoStore: [AppInfo] = [] + @Published var trashedFiles: [AppInfo] = [] @Published var zombieFile: ZombieFile @Published var sortedApps: [AppInfo] = [] @Published var selectedItems = Set() diff --git a/Pearcleaner/Logic/ReversePathsFetch.swift b/Pearcleaner/Logic/ReversePathsFetch.swift index 919b4ea..ad98e0d 100644 --- a/Pearcleaner/Logic/ReversePathsFetch.swift +++ b/Pearcleaner/Logic/ReversePathsFetch.swift @@ -59,9 +59,11 @@ class ReversePathsSearcher { private func processItem(_ itemName: String, itemURL: URL, allPaths: [String], allNames: [String]) { let formattedItemName = itemName.pearFormat() let itemPath = itemURL.path.pearFormat() + let itemLastPathComponent = itemURL.lastPathComponent.pearFormat() guard !skipped.contains(where: { formattedItemName.contains($0) }), - !allPaths.contains(itemPath), +// !allPaths.contains(itemPath), + !allPaths.contains(where: { $0 == itemPath || $0.hasSuffix("/\(itemLastPathComponent)") }), !allNames.contains(formattedItemName), isSupportedFileType(at: itemURL.path) else { return diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index 69776ca..6221afe 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -28,8 +28,8 @@ struct SimpleButtonStyle: ButtonStyle { .resizable() .scaledToFit() .frame(width: 20) - if label != "" { - Text(label!) + if let label = label, !label.isEmpty { + Text(label) } } .foregroundColor(hovered ? color : color.opacity(0.5)) diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index 1c6f6a4..a89f8b9 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -130,13 +130,47 @@ func checkAndRequestAccessibilityAccess(appState: AppState) -> Bool { } } +// Check app directory based on user permission +func checkAppDirectoryAndUserRole(completion: @escaping ((isInCorrectDirectory: Bool, isAdmin: Bool)) -> Void) { + isCurrentUserAdmin { isAdmin in + let bundlePath = Bundle.main.bundlePath as NSString + let applicationsDir = "/Applications" + let userApplicationsDir = "\(home)/Applications" -// Check if app is installed in /Applications directory -func isAppInApplicationsDir() -> Bool { - if let bundlePath = Bundle.main.bundlePath as NSString? { - return bundlePath.deletingLastPathComponent == "/Applications" + var isInCorrectDirectory = false + + if isAdmin { + // Admins can have the app in either /Applications or ~/Applications + isInCorrectDirectory = bundlePath.deletingLastPathComponent == applicationsDir || + bundlePath.deletingLastPathComponent == userApplicationsDir + } else { + // Standard users should only have the app in ~/Applications + isInCorrectDirectory = bundlePath.deletingLastPathComponent == userApplicationsDir + } + + // Return both conditions: if the app is in the correct directory and if the user is an admin + completion((isInCorrectDirectory, isAdmin)) + } +} + + +// Check if user is admin or standard user +func isCurrentUserAdmin(completion: @escaping (Bool) -> Void) { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/bin/zsh") // Using zsh, macOS default shell + process.arguments = ["-c", "groups $(whoami) | grep -q ' admin '"] + + process.terminationHandler = { process in + // On macOS, a process's exit status of 0 indicates success (admin group found in this context) + completion(process.terminationStatus == 0) + } + + do { + try process.run() + } catch { + print("Failed to execute command: \(error)") + completion(false) } - return false } // Check if appearance is dark mode diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index ad67510..bb107fe 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -118,6 +118,7 @@ struct PearcleanerApp: App { }, icon: selectedMenubarIcon) } + #if !DEBUG Task { @@ -157,7 +158,7 @@ struct PearcleanerApp: App { .windowResizability(.contentMinSize) .commands { AppCommands(appState: appState, locations: locations, fsm: fsm) - CommandGroup(replacing: .newItem, addition: { }) +// CommandGroup(replacing: .newItem, addition: { }) } @@ -185,29 +186,33 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { return !menubarEnabled } - -//#if !DEBUG -// func windowShouldClose(_ sender: NSWindow) -> Bool { -// let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled") -// if menubarEnabled { -// findAndHideWindows(named: ["Pearcleaner"]) -// return false -// } else { -// return true -// } -// } -//#endif - func applicationDidFinishLaunching(_ notification: Notification) { let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled") if menubarEnabled { findAndHideWindows(named: ["Pearcleaner"]) NSApplication.shared.setActivationPolicy(.accessory) } -// Link window to delegate -// let mainWindow = NSApp.windows[0] -// mainWindow.delegate = self + } + + func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + let windowSettings = WindowSettings() + + if !flag { + // No visible windows, so let's open a new one + for window in sender.windows { + window.title = "Pearcleaner" + window.makeKeyAndOrderFront(self) + print(windowSettings.loadWindowSettings()) + updateOnMain(after: 0.1, { + resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") + print(window.title) + }) + } + return true // Indicates you've handled the re-open + } + // Return true if you want the application to proceed with its default behavior + return false } } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index a90e251..e03cdc6 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -37,12 +37,16 @@ struct FilesView: View { ProgressView() .progressViewStyle(.linear) .frame(width: 400, height: 10) - Image(systemName: "\(elapsedTime).circle") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 16, height: 16) + Text("\(elapsedTime)") + .font(.caption) .foregroundStyle((.gray.opacity(0.8))) .opacity(elapsedTime == 0 ? 0 : 1) +// Image(systemName: "\(elapsedTime).circle") +// .resizable() +// .aspectRatio(contentMode: .fit) +// .frame(width: 16, height: 16) +// .foregroundStyle((.gray.opacity(0.8))) +// .opacity(elapsedTime == 0 ? 0 : 1) } Spacer() @@ -62,9 +66,26 @@ struct FilesView: View { } else { // Titlebar if !regularWin { - HStack() { + HStack(spacing: 0) { + Spacer() + if instantSearch { + Button("Rescan") { + updateOnMain { + appState.showProgress.toggle() + let pathFinder = AppPathFinder(appInfo: appState.appInfo, appState: appState, locations: locations) { + updateOnMain { + appState.showProgress = false + } + } + pathFinder.findPaths() + } + } + .buttonStyle(NavButtonBottomBarStyle(image: "arrow.counterclockwise.circle.fill", help: "Rescan files")) + } + + Button("Close") { updateOnMain { appState.appInfo = AppInfo.empty @@ -122,61 +143,99 @@ struct FilesView: View { Text("\(appState.appInfo.fileSize.count > 1 ? "\(appState.appInfo.fileSize.count) items" : "\(appState.appInfo.fileSize.count) item")").font(.callout).underline().foregroundStyle((.gray.opacity(0.8))) } - } - - HStack(alignment: .center, spacing: 10) { - - Spacer() - - if appState.appInfo.webApp { - Text("web") - .font(.footnote) - .foregroundStyle(Color("mode").opacity(0.5)) - .frame(minWidth: 30, minHeight: 15) - .padding(2) - .background(Color("mode").opacity(0.1)) - .clipShape(.capsule) - - } - - if appState.appInfo.wrapped { - Text("iOS") - .font(.footnote) - .foregroundStyle(Color("mode").opacity(0.5)) - .frame(minWidth: 30, minHeight: 15) - .padding(2) - .background(Color("mode").opacity(0.1)) - .clipShape(.capsule) - - } - - Text(appState.appInfo.system ? "system" : "user") - .font(.footnote) - .foregroundStyle(Color("mode").opacity(0.5)) - .frame(minWidth: 30, minHeight: 15) - .padding(2) - .padding(.horizontal, 2) - .background(Color("mode").opacity(0.1)) - .clipShape(.capsule) - - } - } .padding(.horizontal, 20) .padding(.top, 0) } + + // Item selection and sorting toolbar + HStack() { + Toggle("", isOn: Binding( + get: { self.appState.selectedItems.count == self.appState.appInfo.files.count }, + set: { newValue in + updateOnMain { + self.appState.selectedItems = newValue ? Set(self.appState.appInfo.files) : [] + } + } + )) + + + Spacer() + + + HStack(alignment: .center, spacing: 10) { + + if appState.appInfo.webApp { + Text("web") + .font(.footnote) + .foregroundStyle(Color("mode").opacity(0.5)) + .frame(minWidth: 30, minHeight: 15) + .padding(2) + .background(Color("mode").opacity(0.1)) + .clipShape(.capsule) + + } + + if appState.appInfo.wrapped { + Text("iOS") + .font(.footnote) + .foregroundStyle(Color("mode").opacity(0.5)) + .frame(minWidth: 30, minHeight: 15) + .padding(2) + .background(Color("mode").opacity(0.1)) + .clipShape(.capsule) + + } + + Text(appState.appInfo.system ? "system" : "user") + .font(.footnote) + .foregroundStyle(Color("mode").opacity(0.5)) + .frame(minWidth: 30, minHeight: 15) + .padding(2) + .padding(.horizontal, 2) + .background(Color("mode").opacity(0.1)) + .clipShape(.capsule) + } + + + Spacer() + + Button("") { + selectedOption = selectedOption == "Default" ? "Size" : "Default" + } + .buttonStyle(SimpleButtonStyle(icon: selectedOption == "Default" ? "textformat.abc" : "textformat.123", help: selectedOption == "Default" ? "Sorted alphabetically" : "Sorted by size", color: Color("mode"))) + + } + .padding() + + + Divider() - .padding() + .padding(.horizontal) + + ScrollView() { - VStack { + LazyVStack { let sortedFilesSize = appState.appInfo.files.sorted(by: { appState.appInfo.fileSize[$0, default: 0] > appState.appInfo.fileSize[$1, default: 0] }) - let sortedFilesAlpha = appState.appInfo.files +// let sortedFilesAlpha = appState.appInfo.files + let sortedFilesAlpha = appState.appInfo.files.sorted { firstURL, secondURL in + let isFirstPathApp = firstURL.pathExtension == "app" + let isSecondPathApp = secondURL.pathExtension == "app" + if isFirstPathApp, !isSecondPathApp { + return true // .app extension always comes first + } else if !isFirstPathApp, isSecondPathApp { + return false + } else { + // If neither or both are .app, sort alphabetically + return firstURL.lastPathComponent.pearFormat() < secondURL.lastPathComponent.pearFormat() + } + } let sort = selectedOption == "Default" ? sortedFilesAlpha : sortedFilesSize @@ -195,33 +254,17 @@ struct FilesView: View { .padding() } + Spacer() HStack() { - Picker("", selection: Binding( - get: { appState.selectedItems.count == appState.appInfo.files.count ? true : false }, - set: { newValue in - updateOnMain { - appState.selectedItems = newValue ? Set(appState.appInfo.files) : [] - } - } - )) { - Image(systemName: "checkmark.square").tag(true) - Image(systemName: "square").tag(false) - } - .pickerStyle(SegmentedPickerStyle()) - .frame(width: 100) - .offset(x: -8) - .help("Item Selection") - Spacer() if !appState.selectedItems.isEmpty { Button("Uninstall") { Task { updateOnMain { -// appState.appInfo = AppInfo.empty search = "" if !regularWin { appState.currentView = .apps @@ -254,13 +297,27 @@ struct FilesView: View { launchctl(load: true) } } - // Remove app from app list - removeApp(appState: appState, withId: appState.appInfo.id) + // Brew cleanup if enabled if brew { caskCleanup(app: appState.appInfo.appName) } - // Clear out AppInfo state + + // Remove app from app list if all app files were removed + if appState.appInfo.files.count == selectedItemsArray.count { + removeApp(appState: appState, withId: appState.appInfo.id) + } else { + // Add deleted appInfo object to trashed array + appState.appInfo.files = [] + appState.appInfo.fileSize = [:] + appState.trashedFiles.append(appState.appInfo) + + // Clear out appInfoStore object + if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) { + appState.appInfoStore[index] = .empty + } + } + appState.appInfo = AppInfo.empty } } @@ -278,13 +335,6 @@ struct FilesView: View { Spacer() - Picker("", selection: $selectedOption) { - Image(systemName: "textformat.abc").tag("Default") - Image(systemName: "number").tag("Size") - } - .pickerStyle(SegmentedPickerStyle()) - .frame(width: 100) - .help("Sorting alphabetically or by size") } } diff --git a/Pearcleaner/Views/MenuBarMiniAppView.swift b/Pearcleaner/Views/MenuBarMiniAppView.swift index ef96060..4e42fd5 100644 --- a/Pearcleaner/Views/MenuBarMiniAppView.swift +++ b/Pearcleaner/Views/MenuBarMiniAppView.swift @@ -111,37 +111,6 @@ struct MenuBarMiniAppView: View { .buttonStyle(SimpleButtonStyle(icon: "gear", label: "Settings", help: "Settings", color: Color("mode"))) } - - -// Button("Leftover Files") { -// showMenu = false -// withAnimation(.easeInOut(duration: 0.5)) { -// showPopover = false -// updateOnMain() { -// appState.appInfo = .empty -// appState.selectedZombieItems = [] -// if appState.zombieFile.fileSize.keys.count == 0 { -// appState.currentView = .zombie -// appState.showProgress.toggle() -// showPopover.toggle() -// if instantSearch { -// reversePathsSearch(appState: appState, locations: locations) -// } else { -// loadAllPaths(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) -// } -// } else { -// appState.currentView = .zombie -// showPopover.toggle() -// } -// } -// -// } -// } -// .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", label: "Leftover Files", help: "Leftover Files", color: Color("mode"))) - - - - Button("Quit") { NSApp.terminate(nil) } diff --git a/Pearcleaner/Views/MiniMode.swift b/Pearcleaner/Views/MiniMode.swift index 7de3f03..06b1d07 100644 --- a/Pearcleaner/Views/MiniMode.swift +++ b/Pearcleaner/Views/MiniMode.swift @@ -50,9 +50,18 @@ struct MiniMode: View { } .interactiveDismissDisabled(popoverStay) .background( - Rectangle() - .fill(Color("pop")) - .padding(-80) + Group { + if glass { + GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) + } else { + Rectangle() + .fill(Color("pop")) + .padding(-80) + } + } +// Rectangle() +// .fill(Color("pop")) +// .padding(-80) ) .frame(width: 650, height: 550) diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index 48ed057..2f19f5f 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -33,9 +33,11 @@ struct ZombieView: View { } let sortedFilteredFiles = filteredFiles.sorted(by: { - selectedOption == "Default" ? - $0.key.lastPathComponent < $1.key.lastPathComponent : - $0.value > $1.value + if selectedOption == "Default" { + return $0.key.lastPathComponent.pearFormat() < $1.key.lastPathComponent.pearFormat() + } else { + return $0.value > $1.value + } }).map { $0.key } let totalSize = filteredFiles.values.reduce(0, +) @@ -56,12 +58,14 @@ struct ZombieView: View { ProgressView() .progressViewStyle(.linear) .frame(width: 400, height: 10) - Image(systemName: "\(elapsedTime).circle") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 16, height: 16) + Text("\(elapsedTime)") + .font(.caption) .foregroundStyle((.gray.opacity(0.8))) - .opacity(elapsedTime == 0 ? 0 : 1) +// Image(systemName: "\(elapsedTime).circle") +// .resizable() +// .aspectRatio(contentMode: .fit) +// .frame(width: 16, height: 16) +// .foregroundStyle((.gray.opacity(0.8))) } @@ -154,15 +158,45 @@ struct ZombieView: View { .padding(.top, 0) } - SearchBarMiniBottom(search: $searchZ) - .padding(.top) +// SearchBarMiniBottom(search: $searchZ) +// .padding(.top) +// .padding(.horizontal) + + // Item selection and sorting toolbar + HStack { + Toggle("", isOn: Binding( + get: { appState.selectedZombieItems.count == appState.zombieFile.fileSize.count }, + set: { newValue in + updateOnMain { + appState.selectedZombieItems = newValue ? Set(appState.zombieFile.fileSize.keys) : [] + } + } + )) + + SearchBarMiniBottom(search: $searchZ) +// .padding(.top) + .padding(.horizontal) + +// Spacer() + + Button("") { + selectedOption = selectedOption == "Default" ? "Size" : "Default" + } + .buttonStyle(SimpleButtonStyle(icon: selectedOption == "Default" ? "textformat.abc" : "textformat.123", help: selectedOption == "Default" ? "Sorted alphabetically" : "Sorted by size", color: Color("mode"))) + + + } + .padding(.horizontal) + .padding(.vertical) + Divider() - .padding() + .padding(.horizontal) + + ScrollView() { LazyVStack { - 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:)) @@ -176,27 +210,28 @@ struct ZombieView: View { } } } - + .padding() } - .padding() + + Spacer() HStack() { - Picker("", selection: Binding( - get: { appState.selectedZombieItems.count == appState.zombieFile.fileSize.count ? true : false }, - set: { newValue in - updateOnMain { - appState.selectedZombieItems = newValue ? Set(appState.zombieFile.fileSize.keys) : [] - } - } - )) { - Image(systemName: "checkmark.square").tag(true) - Image(systemName: "square").tag(false) - } - .pickerStyle(SegmentedPickerStyle()) - .frame(width: 100) - .offset(x: -8) - .help("Item Selection") +// Picker("", selection: Binding( +// get: { appState.selectedZombieItems.count == appState.zombieFile.fileSize.count ? true : false }, +// set: { newValue in +// updateOnMain { +// appState.selectedZombieItems = newValue ? Set(appState.zombieFile.fileSize.keys) : [] +// } +// } +// )) { +// Image(systemName: "checkmark.square").tag(true) +// Image(systemName: "square").tag(false) +// } +// .pickerStyle(SegmentedPickerStyle()) +// .frame(width: 100) +// .offset(x: -8) +// .help("Item Selection") Spacer() @@ -238,13 +273,13 @@ struct ZombieView: View { Spacer() - Picker("", selection: $selectedOption) { - Image(systemName: "textformat.abc").tag("Default") - Image(systemName: "number").tag("Size") - } - .pickerStyle(SegmentedPickerStyle()) - .frame(width: 100) - .help("Sorting alphabetically or by size") +// Picker("", selection: $selectedOption) { +// Image(systemName: "textformat.abc").tag("Default") +// Image(systemName: "number").tag("Size") +// } +// .pickerStyle(SegmentedPickerStyle()) +// .frame(width: 100) +// .help("Sorting alphabetically or by size") } } diff --git a/Pearcleaner/Windows/UpdateView.swift b/Pearcleaner/Windows/UpdateView.swift index 72461f5..07fb3fd 100644 --- a/Pearcleaner/Windows/UpdateView.swift +++ b/Pearcleaner/Windows/UpdateView.swift @@ -10,8 +10,9 @@ import SwiftUI struct UpdateView: View { @EnvironmentObject var appState: AppState - let isAppInAppsDir = isAppInApplicationsDir() - + @State private var isAppInCorrectDirectory: Bool = false + @State private var isUserAdmin: Bool = false + var body: some View { VStack(spacing: 5) { HStack { @@ -52,10 +53,11 @@ struct UpdateView: View { Spacer() - if !isAppInAppsDir { - Text("Please move Pearcleaner to the **Applications** folder before updating!") + if !isAppInCorrectDirectory { + Text("To avoid permission issues, please move Pearcleaner to the \(isUserAdmin ? "/Applications" : "\(home)/Applications") folder before updating!") .font(.callout) - .foregroundStyle(.red) + .foregroundStyle(Color.accentColor) + .padding(.horizontal) .padding(.top, 0) } @@ -97,6 +99,12 @@ struct UpdateView: View { } .padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0)) + .onAppear { + checkAppDirectoryAndUserRole { result in + isAppInCorrectDirectory = result.isInCorrectDirectory + isUserAdmin = result.isAdmin + } + }