diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index 5917312..286640a 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -19,7 +19,7 @@ class AppState: ObservableObject { @Published var sortedApps: [AppInfo] = [] @Published var selectedItems = Set() @Published var currentView = CurrentDetailsView.empty - @Published var currentPage = CurrentPage.installed + @Published var currentPage = CurrentPage.applications @Published var showAlert: Bool = false @Published var sidebar: Bool = true @Published var reload: Bool = false @@ -193,15 +193,17 @@ enum Arch { case empty } -enum CurrentPage:Int +enum CurrentPage:Int, CaseIterable, Identifiable { - case installed - case uninstalled + case applications + case orphans + + var id: Int { rawValue } var title: String { switch self { - case .installed: return "Installed" - case .uninstalled: return "Uninstalled" + case .applications: return "Applications" + case .orphans: return "Orphaned Files" } } } diff --git a/Pearcleaner/Logic/Logic.swift b/Pearcleaner/Logic/Logic.swift index 070346d..7b63662 100644 --- a/Pearcleaner/Logic/Logic.swift +++ b/Pearcleaner/Logic/Logic.swift @@ -144,7 +144,7 @@ func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locatio @AppStorage("settings.interface.animationEnabled") var animationEnabled: Bool = true updateOnMain { - appState.leftoverProgress.0 = "Finding leftover files, please wait..." + appState.leftoverProgress.0 = "Finding orphaned files, please wait..." } ReversePathsSearcher(appState: appState, locations: locations, fsm: fsm, sortedApps: allApps).reversePathsSearch { updateOnMain { diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index a92c4c0..0e7443f 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -91,14 +91,14 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations, f } - // Private function to list leftover files for uninstall, using the provided path + // Private function to list orphaned files for uninstall, using the provided path func listLeftoverFiles() { - print("[BETA] Pearcleaner CLI | List Leftover Files:\n") + print("[BETA] Pearcleaner CLI | List Orphaned Files:\n") // Get installed apps for filtering let sortedApps = getSortedApps(paths: fsm.folderPaths) - // Find leftover files + // Find orphaned files let foundPaths = ReversePathsSearcher(locations: locations, fsm: fsm, sortedApps: sortedApps) .reversePathsSearchCLI() @@ -106,7 +106,7 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations, f for path in foundPaths { print(path.path) } - print("\nFound \(foundPaths.count) leftover files.\n") + print("\nFound \(foundPaths.count) orphaned files.\n") } // Private function to uninstall the application bundle at a given path @@ -163,23 +163,23 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations, f } } - // Private function to remove the leftover files + // Private function to remove the orphaned files func removeLeftoverFiles() { - print("[BETA] Pearcleaner CLI | Remove Leftover Files:\n") + print("[BETA] Pearcleaner CLI | Remove Orphaned Files:\n") // Get installed apps for filtering let sortedApps = getSortedApps(paths: fsm.folderPaths) - // Find leftover files + // Find orphaned files let foundPaths = ReversePathsSearcher(locations: locations, fsm: fsm, sortedApps: sortedApps) .reversePathsSearchCLI() let success = moveFilesToTrashCLI(at: foundPaths) if success { - print("Leftover files have been moved to the trash successfully.\n") + print("Orphaned files have been moved to the trash successfully.\n") exit(0) } else { - print("Failed to move leftover files to trash.\n") + print("Failed to move orphaned files to trash.\n") exit(1) } } @@ -197,8 +197,8 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations, f exit(0) } - // Handle --listlf or -lf option with a path argument for listing leftover files - if options.contains("--list-leftover") || options.contains("-lf") { + // Handle --listlf or -lf option with a path argument for listing orphaned files + if options.contains("--list-orphaned") || options.contains("-lo") { listLeftoverFiles() exit(0) } @@ -217,8 +217,8 @@ func processCLI(arguments: [String], appState: AppState, locations: Locations, f exit(0) } - // Handle --uninstall-lf or -ulf option with a path argument for listing leftover files - if options.contains("--remove-leftover") || options.contains("-rl") { + // Handle --uninstall-lf or -ulf option with a path argument for listing orphaned files + if options.contains("--remove-orphaned") || options.contains("-ro") { removeLeftoverFiles() exit(0) } @@ -235,10 +235,10 @@ func displayHelp() { [BETA] Pearcleaner CLI | Usage: --list , -l List application files available for uninstall at the specified path - --list-leftover, -lf List leftover files available for removal + --list-orphaned, -lo List orphaned files available for removal --uninstall , -u Uninstall only the application bundle at the specified path --uninstall-all , -ua Uninstall application bundle and ALL related files at the specified path - --remove-leftover, -rl Remove ALL leftover files (To ignore files, add them to the exception list within Pearcleaner settings) + --remove-orphaned, -ro Remove ALL orphaned files (To ignore files, add them to the exception list within Pearcleaner settings) --help, -h Show this help message """) diff --git a/Pearcleaner/Settings/Folders.swift b/Pearcleaner/Settings/Folders.swift index 0235f1c..38c6e3e 100644 --- a/Pearcleaner/Settings/Folders.swift +++ b/Pearcleaner/Settings/Folders.swift @@ -110,7 +110,7 @@ struct FolderSettingsTab: View { PearGroupBox(header: { HStack(spacing: 0) { Text("Exclude these files and folders from leftover search").font(.title2) - InfoButton(text: "Add files or folders that will be ignored when searching for leftover files. Click a path to remove it from the list.") + InfoButton(text: "Add files or folders that will be ignored when searching for orphaned files. Click a path to remove it from the list.") .padding(.leading, 5) Spacer() } @@ -287,7 +287,7 @@ class FolderSettingsManager: ObservableObject { - // Leftover files ////////////////////////////////////////////////////////////////////////////////// + // Orphaned files ////////////////////////////////////////////////////////////////////////////////// func addPathZ(_ path: String) { let sanitizedPath = path.hasPrefix("/private") ? String(path.dropFirst(8)) : path diff --git a/Pearcleaner/Settings/Tips.swift b/Pearcleaner/Settings/Tips.swift index 4abfdde..91fe346 100644 --- a/Pearcleaner/Settings/Tips.swift +++ b/Pearcleaner/Settings/Tips.swift @@ -29,7 +29,7 @@ struct TipsSettingsTab: View { Text("6. On the Files view again, you can hit CMD + E to export a list of the found files or from the Tools menubar item") Text("7. CMD + U will check for updates manually") Text("8. CMD + R will refresh the app list. This can also be done from the refresh button in the search bar") - Text("9. In the search bar menu button, you will also find Leftover Files which will find files leftover by previously uninstalled applications") + Text("9. In the search bar menu button, you will also find Orphaned Files which will find files leftover by previously uninstalled applications") Text("10. You can enable a menubar icon from Settings") Text("11. You can enable mini mode version of the UI from Settings") Text("12. You can enable a Finder extension from Settings which allows you to uninstall apps by right clicking them in Finder") diff --git a/Pearcleaner/Views/AppSearchView.swift b/Pearcleaner/Views/AppSearchView.swift index 467a0bd..b86be0b 100644 --- a/Pearcleaner/Views/AppSearchView.swift +++ b/Pearcleaner/Views/AppSearchView.swift @@ -88,7 +88,7 @@ struct AppSearchView: View { Button("More") { self.showMenu.toggle() } - .buttonStyle(SimpleButtonStyle(icon: "ellipsis.circle", help: "More", size: 16, rotate: true)) + .buttonStyle(SimpleButtonStyle(icon: "ellipsis.circle", help: "More", size: 16, rotate: false)) .popover(isPresented: $showMenu) { VStack(alignment: .leading) { @@ -97,7 +97,7 @@ struct AppSearchView: View { selectedSortAlpha.toggle() } } - .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Sorting: \(selectedSortAlpha ? "ABC" : "123")", help: "Can also click on User/System headers to toggle this", size: 5)) + .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Sorting: \(selectedSortAlpha ? "Name" : "Size")", help: "Sort app list alphabetically by name or by size", size: 5)) if mini && !menubarEnabled { Button("") { @@ -111,7 +111,7 @@ struct AppSearchView: View { } - Button("Leftover Files") { + Button("Orphaned Files") { showMenu = false withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) { showPopover = false @@ -127,7 +127,7 @@ struct AppSearchView: View { } } } - .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Leftover Files", help: "Leftover Files", size: 5)) + .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Orphaned Files", help: "Orphaned Files", size: 5)) if #available(macOS 14.0, *) { @@ -155,6 +155,7 @@ struct AppSearchView: View { } .padding() .background(backgroundView(themeManager: themeManager, glass: glass).padding(-80)) + .frame(width: 150) } } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index 45ddbd7..97fd883 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -26,8 +26,6 @@ struct FilesView: View { @Environment(\.colorScheme) var colorScheme @Binding var showPopover: Bool @Binding var search: String -// @State private var elapsedTime = 0 -// @State private var timer: Timer? = nil var body: some View { @@ -59,52 +57,32 @@ struct FilesView: View { ProgressView().controlSize(.small) } -// Text("Searching the file system").font(.title3) -// .foregroundStyle((.primary.opacity(0.5))) -// -// ProgressView() -// .progressViewStyle(.linear) -// .frame(width: 400, height: 10) -// -// Text("\(elapsedTime)") -// .font(.title).monospacedDigit() -// .foregroundStyle((.primary.opacity(0.5))) -// .opacity(elapsedTime == 0 ? 0 : 1) -// .contentTransition(.numericText()) - Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) .transition(.opacity) -// .onAppear { -// self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in -// withAnimation { -// self.elapsedTime += 1 -// } -// } -// } -// .onDisappear { -// self.timer?.invalidate() -// self.timer = nil -// self.elapsedTime = 0 -// } } else { + // Titlebar HStack(spacing: 0) { Spacer() - Button("Close") { - updateOnMain { - appState.appInfo = AppInfo.empty - search = "" - appState.currentView = .apps - showPopover = false + if mini || menubarEnabled { + Button("Close") { + updateOnMain { + appState.appInfo = AppInfo.empty + search = "" + appState.currentView = .apps + showPopover = false + } } + .buttonStyle(SimpleButtonStyle(icon: "x.circle", iconFlip: "x.circle.fill", help: "Close")) } - .buttonStyle(SimpleButtonStyle(icon: "x.circle", iconFlip: "x.circle.fill", help: "Close")) + + } - .padding(.top, 6) + .padding(.top, (mini || menubarEnabled) ? 6 : 30) .padding(.trailing, (mini || menubarEnabled) ? 6 : 0) VStack(spacing: 0) { diff --git a/Pearcleaner/Views/RegularMode.swift b/Pearcleaner/Views/RegularMode.swift index 41a5ffd..7b5afb4 100644 --- a/Pearcleaner/Views/RegularMode.swift +++ b/Pearcleaner/Views/RegularMode.swift @@ -29,88 +29,101 @@ struct RegularMode: View { var body: some View { // Main App Window - VStack(spacing: 0) { + ZStack() { - // Tab View - CustomTabView(content: [ - ( - title: "Installed", - icon: "externaldrive", - view: AnyView ( - HStack(alignment: .center, spacing: 0) { + if appState.currentPage == .applications { + HStack(alignment: .center, spacing: 0) { - // App List - HStack(spacing: 0){ + // App List + HStack(spacing: 0){ - if appState.reload { - VStack { - Spacer() - ProgressView() { - Text("Gathering app details") - .font(.callout) - .foregroundStyle(.primary.opacity(0.5)) - .padding(5) - } - Spacer() - } - .frame(width: sidebarWidth) - .padding(.vertical) - } else { - AppSearchView(glass: glass, menubarEnabled: menubarEnabled, mini: mini, search: $search, showPopover: $showPopover, isMenuBar: $isMenuBar) - .frame(width: sidebarWidth) - - } - - } - .background(backgroundView(themeManager: themeManager, darker: true, glass: glass)) - .transition(.opacity) - - SlideableDivider(dimension: $sidebarWidth) - .background(backgroundView(themeManager: themeManager)) - .zIndex(3) - - - // Details View - HStack(spacing: 0) { + if appState.reload { + VStack { Spacer() - Group { - if appState.currentView == .empty || appState.currentView == .apps { - AppDetailsEmptyView(showPopover: $showPopover) - } else if appState.currentView == .files { - FilesView(showPopover: $showPopover, search: $search) - .id(appState.appInfo.id) - } else if appState.currentView == .zombie { - ZombieView(showPopover: $showPopover, search: $search) - .id(appState.appInfo.id) - } + ProgressView() { + Text("Gathering app details") + .font(.callout) + .foregroundStyle(.primary.opacity(0.5)) + .padding(5) } - .transition(.opacity) Spacer() } - .zIndex(2) - .background(backgroundView(themeManager: themeManager)) + .frame(width: sidebarWidth) + .padding(.vertical) + } else { + AppSearchView(glass: glass, menubarEnabled: menubarEnabled, mini: mini, search: $search, showPopover: $showPopover, isMenuBar: $isMenuBar) + .frame(width: sidebarWidth) + } - ) - ), - ( - title: "Uninstalled", - icon: "externaldrive.badge.minus", - view: AnyView( - ZombieView(showPopover: $showPopover, search: $search) - .background(backgroundView(themeManager: themeManager)) - .onAppear { - if appState.zombieFile.fileSize.keys.isEmpty { - appState.showProgress.toggle() - } - withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) { - if appState.zombieFile.fileSize.keys.isEmpty { - reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm) - } - } + + } + .background(backgroundView(themeManager: themeManager, darker: true, glass: glass)) + .transition(.opacity) + + SlideableDivider(dimension: $sidebarWidth) + .background(backgroundView(themeManager: themeManager)) + .zIndex(3) + + + // Details View + HStack(spacing: 0) { + Spacer() + Group { + if appState.currentView == .empty || appState.currentView == .apps { + AppDetailsEmptyView(showPopover: $showPopover) + } else if appState.currentView == .files { + FilesView(showPopover: $showPopover, search: $search) + .id(appState.appInfo.id) + } else if appState.currentView == .zombie { + ZombieView(showPopover: $showPopover, search: $search) + .id(appState.appInfo.id) } - ) - ), - ]) + } + .transition(.opacity) + Spacer() + } + .zIndex(2) + .background(backgroundView(themeManager: themeManager)) + } + + } else if appState.currentPage == .orphans { + ZombieView(showPopover: $showPopover, search: $search) + .background(backgroundView(themeManager: themeManager)) + .onAppear { + if appState.zombieFile.fileSize.keys.isEmpty { + appState.showProgress.toggle() + } + withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) { + if appState.zombieFile.fileSize.keys.isEmpty { + reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, fsm: fsm) + } + } + } + } + + VStack(spacing: 0) { + HStack { + Spacer() + Picker("", selection: $appState.currentPage) { + ForEach(CurrentPage.allCases) { page in + Text(page.title) + .font(.title3) + .tag(page) + } + } + .buttonStyle(.borderless) + .padding(2) + .padding(.vertical, 2) + .background { + RoundedRectangle(cornerRadius: 8) + .strokeBorder(.secondary.opacity(0.5), lineWidth: 1) + } + } + .padding(6) + + + Spacer() + } } .frame(minWidth: 900, minHeight: 600) diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index 2c3ae19..e51b7d9 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -64,17 +64,19 @@ struct ZombieView: View { HStack(spacing: 0) { Spacer() - Button("Close") { - updateOnMain { - appState.appInfo = AppInfo.empty - search = "" - appState.currentView = .apps - showPopover = false + if mini || menubarEnabled { + Button("Close") { + updateOnMain { + appState.appInfo = AppInfo.empty + search = "" + appState.currentView = .apps + showPopover = false + } } + .buttonStyle(SimpleButtonStyle(icon: "x.circle", iconFlip: "x.circle.fill", help: "Close")) } - .buttonStyle(SimpleButtonStyle(icon: "x.circle", iconFlip: "x.circle.fill", help: "Close")) } - .padding(.top, 6) + .padding(.top, (mini || menubarEnabled) ? 6 : 30) .padding(.trailing, (mini || menubarEnabled) ? 6 : 0) @@ -94,7 +96,7 @@ struct ZombieView: View { VStack(alignment: .leading, spacing: 10){ HStack { - Text("Leftover Files").font(.title).fontWeight(.bold) + Text("Orphaned Files").font(.title).fontWeight(.bold) Spacer() } Text("Remaining files and folders from previous applications")