This commit is contained in:
Alin
2024-03-22 18:16:55 -06:00
parent 5350a8f310
commit 7906cd70f7
13 changed files with 311 additions and 186 deletions
+5 -5
View File
@@ -245,12 +245,12 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C77B90222AF2D616009CC655 /* FilesView.swift */, C77B90222AF2D616009CC655 /* FilesView.swift */,
C71848432B8D2D600046CB13 /* ZombieView.swift */,
C76D08542AF89CDE00D07867 /* RegularMode.swift */, C76D08542AF89CDE00D07867 /* RegularMode.swift */,
C7D31D472AFEB23700C7ED9E /* TopBar.swift */, C7D31D472AFEB23700C7ED9E /* TopBar.swift */,
C7045A292B03FAF000376976 /* TopBarMini.swift */, C7045A292B03FAF000376976 /* TopBarMini.swift */,
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */, C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
C7045A272B03E71D00376976 /* MiniMode.swift */, C7045A272B03E71D00376976 /* MiniMode.swift */,
C71848432B8D2D600046CB13 /* ZombieView.swift */,
C7FB173A2B96321300B96F9A /* AppsListView.swift */, C7FB173A2B96321300B96F9A /* AppsListView.swift */,
C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */, C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */,
); );
@@ -575,7 +575,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 26; CURRENT_PROJECT_VERSION = 27;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU; DEVELOPMENT_TEAM = BK8443AXLU;
@@ -593,7 +593,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 3.3.0; MARKETING_VERSION = 3.3.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -610,7 +610,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 26; CURRENT_PROJECT_VERSION = 27;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU; DEVELOPMENT_TEAM = BK8443AXLU;
@@ -628,7 +628,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 3.3.0; MARKETING_VERSION = 3.3.1;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
+16 -5
View File
@@ -74,12 +74,12 @@ struct AppCommands: Commands {
{ {
undoTrash(appState: appState) { undoTrash(appState: appState) {
let sortedApps = getSortedApps(paths: fsm.folderPaths, 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 appState.sortedApps = sortedApps
// if instantSearch { for app in appState.trashedFiles {
// loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) let pathFinder = AppPathFinder(appInfo: app, appState: appState, locations: locations)
// } pathFinder.findPaths()
}
} }
} }
} label: { } label: {
@@ -88,6 +88,17 @@ struct AppCommands: Commands {
.keyboardShortcut("z", modifiers: .command) .keyboardShortcut("z", modifiers: .command)
// Button
// {
// updateOnMain {
// appState.currentView = .zombie
// }
// } label: {
// Label("Zombie", systemImage: "clear")
// }
// .keyboardShortcut("f", modifiers: .command)
} }
+2 -1
View File
@@ -210,9 +210,10 @@ class AppPathFinder {
self.appState.appInfo = self.appInfo self.appState.appInfo = self.appInfo
self.appState.selectedItems = Set(updatedCollection) 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 { if self.instantSearch {
self.appState.appInfoStore.append(self.appInfo)
self.appState.instantProgress += 1 self.appState.instantProgress += 1
} }
+1
View File
@@ -14,6 +14,7 @@ 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 zombieFile: ZombieFile @Published var zombieFile: ZombieFile
@Published var sortedApps: [AppInfo] = [] @Published var sortedApps: [AppInfo] = []
@Published var selectedItems = Set<URL>() @Published var selectedItems = Set<URL>()
+3 -1
View File
@@ -59,9 +59,11 @@ class ReversePathsSearcher {
private func processItem(_ itemName: String, itemURL: URL, allPaths: [String], allNames: [String]) { private func processItem(_ itemName: String, itemURL: URL, allPaths: [String], allNames: [String]) {
let formattedItemName = itemName.pearFormat() let formattedItemName = itemName.pearFormat()
let itemPath = itemURL.path.pearFormat() let itemPath = itemURL.path.pearFormat()
let itemLastPathComponent = itemURL.lastPathComponent.pearFormat()
guard !skipped.contains(where: { formattedItemName.contains($0) }), guard !skipped.contains(where: { formattedItemName.contains($0) }),
!allPaths.contains(itemPath), // !allPaths.contains(itemPath),
!allPaths.contains(where: { $0 == itemPath || $0.hasSuffix("/\(itemLastPathComponent)") }),
!allNames.contains(formattedItemName), !allNames.contains(formattedItemName),
isSupportedFileType(at: itemURL.path) else { isSupportedFileType(at: itemURL.path) else {
return return
+2 -2
View File
@@ -28,8 +28,8 @@ struct SimpleButtonStyle: ButtonStyle {
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 20) .frame(width: 20)
if label != "" { if let label = label, !label.isEmpty {
Text(label!) Text(label)
} }
} }
.foregroundColor(hovered ? color : color.opacity(0.5)) .foregroundColor(hovered ? color : color.opacity(0.5))
+39 -5
View File
@@ -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 var isInCorrectDirectory = false
func isAppInApplicationsDir() -> Bool {
if let bundlePath = Bundle.main.bundlePath as NSString? { if isAdmin {
return bundlePath.deletingLastPathComponent == "/Applications" // 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 // Check if appearance is dark mode
+22 -17
View File
@@ -118,6 +118,7 @@ struct PearcleanerApp: App {
}, icon: selectedMenubarIcon) }, icon: selectedMenubarIcon)
} }
#if !DEBUG #if !DEBUG
Task { Task {
@@ -157,7 +158,7 @@ struct PearcleanerApp: App {
.windowResizability(.contentMinSize) .windowResizability(.contentMinSize)
.commands { .commands {
AppCommands(appState: appState, locations: locations, fsm: fsm) 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 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) { func applicationDidFinishLaunching(_ notification: Notification) {
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled") let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
if menubarEnabled { if menubarEnabled {
findAndHideWindows(named: ["Pearcleaner"]) findAndHideWindows(named: ["Pearcleaner"])
NSApplication.shared.setActivationPolicy(.accessory) 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
} }
} }
+90 -40
View File
@@ -37,12 +37,16 @@ struct FilesView: View {
ProgressView() ProgressView()
.progressViewStyle(.linear) .progressViewStyle(.linear)
.frame(width: 400, height: 10) .frame(width: 400, height: 10)
Image(systemName: "\(elapsedTime).circle") Text("\(elapsedTime)")
.resizable() .font(.caption)
.aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16)
.foregroundStyle((.gray.opacity(0.8))) .foregroundStyle((.gray.opacity(0.8)))
.opacity(elapsedTime == 0 ? 0 : 1) .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() Spacer()
@@ -62,9 +66,26 @@ struct FilesView: View {
} else { } else {
// Titlebar // Titlebar
if !regularWin { if !regularWin {
HStack() { HStack(spacing: 0) {
Spacer() 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") { Button("Close") {
updateOnMain { updateOnMain {
appState.appInfo = AppInfo.empty appState.appInfo = AppInfo.empty
@@ -122,14 +143,32 @@ 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))) Text("\(appState.appInfo.fileSize.count > 1 ? "\(appState.appInfo.fileSize.count) items" : "\(appState.appInfo.fileSize.count) item")").font(.callout).underline().foregroundStyle((.gray.opacity(0.8)))
} }
}
}
.padding(.horizontal, 20)
.padding(.top, 0)
} }
HStack(alignment: .center, spacing: 10) { // 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() Spacer()
HStack(alignment: .center, spacing: 10) {
if appState.appInfo.webApp { if appState.appInfo.webApp {
Text("web") Text("web")
.font(.footnote) .font(.footnote)
@@ -160,23 +199,43 @@ struct FilesView: View {
.padding(.horizontal, 2) .padding(.horizontal, 2)
.background(Color("mode").opacity(0.1)) .background(Color("mode").opacity(0.1))
.clipShape(.capsule) .clipShape(.capsule)
} }
Spacer()
Button("") {
selectedOption = selectedOption == "Default" ? "Size" : "Default"
} }
.padding(.horizontal, 20) .buttonStyle(SimpleButtonStyle(icon: selectedOption == "Default" ? "textformat.abc" : "textformat.123", help: selectedOption == "Default" ? "Sorted alphabetically" : "Sorted by size", color: Color("mode")))
.padding(.top, 0)
} }
Divider()
.padding() .padding()
Divider()
.padding(.horizontal)
ScrollView() { ScrollView() {
VStack { LazyVStack {
let sortedFilesSize = appState.appInfo.files.sorted(by: { appState.appInfo.fileSize[$0, default: 0] > appState.appInfo.fileSize[$1, default: 0] }) 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 let sort = selectedOption == "Default" ? sortedFilesAlpha : sortedFilesSize
@@ -195,33 +254,17 @@ struct FilesView: View {
.padding() .padding()
} }
Spacer() Spacer()
HStack() { 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() Spacer()
if !appState.selectedItems.isEmpty { if !appState.selectedItems.isEmpty {
Button("Uninstall") { Button("Uninstall") {
Task { Task {
updateOnMain { updateOnMain {
// appState.appInfo = AppInfo.empty
search = "" search = ""
if !regularWin { if !regularWin {
appState.currentView = .apps appState.currentView = .apps
@@ -254,13 +297,27 @@ struct FilesView: View {
launchctl(load: true) launchctl(load: true)
} }
} }
// Remove app from app list
removeApp(appState: appState, withId: appState.appInfo.id)
// Brew cleanup if enabled // Brew cleanup if enabled
if brew { if brew {
caskCleanup(app: appState.appInfo.appName) 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 appState.appInfo = AppInfo.empty
} }
} }
@@ -278,13 +335,6 @@ struct FilesView: View {
Spacer() 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")
} }
} }
@@ -111,37 +111,6 @@ struct MenuBarMiniAppView: View {
.buttonStyle(SimpleButtonStyle(icon: "gear", label: "Settings", help: "Settings", color: Color("mode"))) .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") { Button("Quit") {
NSApp.terminate(nil) NSApp.terminate(nil)
} }
+9
View File
@@ -50,9 +50,18 @@ struct MiniMode: View {
} }
.interactiveDismissDisabled(popoverStay) .interactiveDismissDisabled(popoverStay)
.background( .background(
Group {
if glass {
GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all)
} else {
Rectangle() Rectangle()
.fill(Color("pop")) .fill(Color("pop"))
.padding(-80) .padding(-80)
}
}
// Rectangle()
// .fill(Color("pop"))
// .padding(-80)
) )
.frame(width: 650, height: 550) .frame(width: 650, height: 550)
+70 -35
View File
@@ -33,9 +33,11 @@ struct ZombieView: View {
} }
let sortedFilteredFiles = filteredFiles.sorted(by: { let sortedFilteredFiles = filteredFiles.sorted(by: {
selectedOption == "Default" ? if selectedOption == "Default" {
$0.key.lastPathComponent < $1.key.lastPathComponent : return $0.key.lastPathComponent.pearFormat() < $1.key.lastPathComponent.pearFormat()
$0.value > $1.value } else {
return $0.value > $1.value
}
}).map { $0.key } }).map { $0.key }
let totalSize = filteredFiles.values.reduce(0, +) let totalSize = filteredFiles.values.reduce(0, +)
@@ -56,12 +58,14 @@ struct ZombieView: View {
ProgressView() ProgressView()
.progressViewStyle(.linear) .progressViewStyle(.linear)
.frame(width: 400, height: 10) .frame(width: 400, height: 10)
Image(systemName: "\(elapsedTime).circle") Text("\(elapsedTime)")
.resizable() .font(.caption)
.aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16)
.foregroundStyle((.gray.opacity(0.8))) .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) .padding(.top, 0)
} }
// 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) SearchBarMiniBottom(search: $searchZ)
.padding(.top) // .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() Divider()
.padding() .padding(.horizontal)
ScrollView() { ScrollView() {
LazyVStack { LazyVStack {
ForEach(filteredAndSortedFiles.0, id: \.self) { file in ForEach(filteredAndSortedFiles.0, id: \.self) { file in
if let fileSize = appState.zombieFile.fileSize[file], let fileIcon = appState.zombieFile.fileIcon[file] { if let fileSize = appState.zombieFile.fileSize[file], let fileIcon = appState.zombieFile.fileIcon[file] {
let iconImage = fileIcon.map(Image.init(nsImage:)) let iconImage = fileIcon.map(Image.init(nsImage:))
@@ -176,27 +210,28 @@ struct ZombieView: View {
} }
} }
} }
}
.padding() .padding()
}
Spacer()
HStack() { HStack() {
Picker("", selection: Binding( // Picker("", selection: Binding(
get: { appState.selectedZombieItems.count == appState.zombieFile.fileSize.count ? true : false }, // get: { appState.selectedZombieItems.count == appState.zombieFile.fileSize.count ? true : false },
set: { newValue in // set: { newValue in
updateOnMain { // updateOnMain {
appState.selectedZombieItems = newValue ? Set(appState.zombieFile.fileSize.keys) : [] // appState.selectedZombieItems = newValue ? Set(appState.zombieFile.fileSize.keys) : []
} // }
} // }
)) { // )) {
Image(systemName: "checkmark.square").tag(true) // Image(systemName: "checkmark.square").tag(true)
Image(systemName: "square").tag(false) // Image(systemName: "square").tag(false)
} // }
.pickerStyle(SegmentedPickerStyle()) // .pickerStyle(SegmentedPickerStyle())
.frame(width: 100) // .frame(width: 100)
.offset(x: -8) // .offset(x: -8)
.help("Item Selection") // .help("Item Selection")
Spacer() Spacer()
@@ -238,13 +273,13 @@ struct ZombieView: View {
Spacer() Spacer()
Picker("", selection: $selectedOption) { // Picker("", selection: $selectedOption) {
Image(systemName: "textformat.abc").tag("Default") // Image(systemName: "textformat.abc").tag("Default")
Image(systemName: "number").tag("Size") // Image(systemName: "number").tag("Size")
} // }
.pickerStyle(SegmentedPickerStyle()) // .pickerStyle(SegmentedPickerStyle())
.frame(width: 100) // .frame(width: 100)
.help("Sorting alphabetically or by size") // .help("Sorting alphabetically or by size")
} }
} }
+12 -4
View File
@@ -10,7 +10,8 @@ import SwiftUI
struct UpdateView: View { struct UpdateView: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
let isAppInAppsDir = isAppInApplicationsDir() @State private var isAppInCorrectDirectory: Bool = false
@State private var isUserAdmin: Bool = false
var body: some View { var body: some View {
VStack(spacing: 5) { VStack(spacing: 5) {
@@ -52,10 +53,11 @@ struct UpdateView: View {
Spacer() Spacer()
if !isAppInAppsDir { if !isAppInCorrectDirectory {
Text("Please move Pearcleaner to the **Applications** folder before updating!") Text("To avoid permission issues, please move Pearcleaner to the \(isUserAdmin ? "/Applications" : "\(home)/Applications") folder before updating!")
.font(.callout) .font(.callout)
.foregroundStyle(.red) .foregroundStyle(Color.accentColor)
.padding(.horizontal)
.padding(.top, 0) .padding(.top, 0)
} }
@@ -97,6 +99,12 @@ struct UpdateView: View {
} }
.padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0)) .padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0))
.onAppear {
checkAppDirectoryAndUserRole { result in
isAppInCorrectDirectory = result.isInCorrectDirectory
isUserAdmin = result.isAdmin
}
}