From 7f1aaf4650cfa9c6d8551076af93c670cde9ff24 Mon Sep 17 00:00:00 2001 From: Alin Date: Mon, 7 Oct 2024 15:15:09 -0600 Subject: [PATCH] v3.9.1 --- Pearcleaner.xcodeproj/project.pbxproj | 8 +- Pearcleaner/Logic/AppInfoFetch.swift | 5 +- Pearcleaner/Logic/AppState.swift | 15 +++ Pearcleaner/Settings/About.swift | 1 + Pearcleaner/Settings/General.swift | 50 ++++----- Pearcleaner/Views/AppSearchView.swift | 82 ++++++++++++-- Pearcleaner/Views/FilesView.swift | 154 +++++++++++++++++--------- Pearcleaner/Views/RegularMode.swift | 10 +- Pearcleaner/Views/ZombieView.swift | 70 ++++++------ 9 files changed, 258 insertions(+), 137 deletions(-) diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index 83e0b3d..7305d18 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -537,8 +537,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_BUILD = 62; - APP_VERSION = 3.9.0; + APP_BUILD = 63; + APP_VERSION = 3.9.1; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -607,8 +607,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_BUILD = 62; - APP_VERSION = 3.9.0; + APP_BUILD = 63; + APP_VERSION = 3.9.1; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/Pearcleaner/Logic/AppInfoFetch.swift b/Pearcleaner/Logic/AppInfoFetch.swift index ead4141..1a1d9d4 100644 --- a/Pearcleaner/Logic/AppInfoFetch.swift +++ b/Pearcleaner/Logic/AppInfoFetch.swift @@ -14,7 +14,8 @@ import AlinFoundation class MetadataAppInfoFetcher { static func getAppInfo(fromMetadata metadata: [String: Any], atPath path: URL) -> AppInfo? { // Extract metadata attributes for known fields - let displayName = metadata["kMDItemDisplayName"] as? String ?? "" + var displayName = metadata["kMDItemDisplayName"] as? String ?? "" + displayName = displayName.replacingOccurrences(of: ".app", with: "").capitalizingFirstLetter() let fsName = metadata["kMDItemFSName"] as? String ?? path.lastPathComponent let appName = displayName.isEmpty ? fsName : displayName @@ -152,7 +153,7 @@ class AppInfoUtils { static func fetchAppIcon(for path: URL, wrapped: Bool, md: Bool = false) -> NSImage? { let iconPath = wrapped ? (md ? path : path.deletingLastPathComponent().deletingLastPathComponent()) : path if let appIcon = getIconForFileOrFolderNS(atPath: iconPath) { - return convertICNSToPNG(icon: appIcon, size: NSSize(width: 100, height: 100)) + return convertICNSToPNG(icon: appIcon, size: NSSize(width: 50, height: 50)) } else { printOS("App Icon not found for app at path: \(path)") return nil diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index 286640a..6914726 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -208,6 +208,21 @@ enum CurrentPage:Int, CaseIterable, Identifiable } } +enum SortOption:Int, CaseIterable, Identifiable { + case alphabetical + case size + case creationDate + case contentChangeDate + case lastUsedDate + + var id: Int { rawValue } + + var title: String { + let titles: [String] = ["App Name", "App Size", "Install Date", "Modified Date", "Last Used Date"] + return titles[rawValue] + } +} + enum CurrentTabView:Int { diff --git a/Pearcleaner/Settings/About.swift b/Pearcleaner/Settings/About.swift index f17daca..3eda6d7 100644 --- a/Pearcleaner/Settings/About.swift +++ b/Pearcleaner/Settings/About.swift @@ -186,6 +186,7 @@ struct Sponsor: Identifiable { let url: URL static let sponsors: [Sponsor] = [ + Sponsor(name: "mzdr (monthly)", url: URL(string: "https://github.com/mzdr")!), Sponsor(name: "chris3ware", url: URL(string: "https://github.com/chris3ware")!), Sponsor(name: "fpuhan", url: URL(string: "https://github.com/fpuhan")!), Sponsor(name: "HungThinhIT", url: URL(string: "https://github.com/HungThinhIT")!), diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index d083138..b5b9533 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -19,7 +19,7 @@ struct GeneralSettingsTab: View { @AppStorage("settings.general.brew") private var brew: Bool = false @AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false @AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false - @AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true +// @AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true @AppStorage("settings.general.sizeType") var sizeType: String = "Real" @State private var isCLISymlinked = false @@ -107,28 +107,28 @@ struct GeneralSettingsTab: View { - HStack(spacing: 0) { - Image(systemName: selectedSortAlpha ? "textformat.abc" : "textformat.123") - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - .padding(.trailing) - .foregroundStyle(.primary) - VStack(alignment: .leading, spacing: 5) { - Text("File list sorting order") - .font(.callout) - .foregroundStyle(.primary) - } - Spacer() - Picker("", selection: $selectedSortAlpha) { - Text("Alphabetical") - .tag(true) - Text("File Size") - .tag(false) - } - .buttonStyle(.borderless) - } - .padding(5) +// HStack(spacing: 0) { +// Image(systemName: selectedSortAlpha ? "textformat.abc" : "textformat.123") +// .resizable() +// .scaledToFit() +// .frame(width: 20, height: 20) +// .padding(.trailing) +// .foregroundStyle(.primary) +// VStack(alignment: .leading, spacing: 5) { +// Text("File list sorting order") +// .font(.callout) +// .foregroundStyle(.primary) +// } +// Spacer() +// Picker("", selection: $selectedSortAlpha) { +// Text("Alphabetical") +// .tag(true) +// Text("File Size") +// .tag(false) +// } +// .buttonStyle(.borderless) +// } +// .padding(5) @@ -151,8 +151,8 @@ struct GeneralSettingsTab: View { .tag("Real") Text("Logical") .tag("Logical") - Text("Finder") - .tag("Finder") +// Text("Finder") +// .tag("Finder") } .buttonStyle(.borderless) diff --git a/Pearcleaner/Views/AppSearchView.swift b/Pearcleaner/Views/AppSearchView.swift index b86be0b..5f79951 100644 --- a/Pearcleaner/Views/AppSearchView.swift +++ b/Pearcleaner/Views/AppSearchView.swift @@ -22,7 +22,7 @@ struct AppSearchView: View { @Binding var showPopover: Bool @State private var showMenu = false @Binding var isMenuBar: Bool - @AppStorage("settings.general.selectedSortAppsList") var selectedSortAlpha: Bool = true + @AppStorage("settings.general.selectedSortAppsList") var selectedSortOption: SortOption = .alphabetical @AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true @@ -81,10 +81,10 @@ struct AppSearchView: View { .buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle", help: "Refresh apps (⌘+R)", size: 16)) } - SearchBar(search: $search, darker: (mini || menubarEnabled) ? false : true, glass: glass) + SearchBar(search: $search, darker: (mini || menubarEnabled) ? false : true, glass: glass, sidebar: false) - if search.isEmpty { + if search.isEmpty && (mini || menubarEnabled) { Button("More") { self.showMenu.toggle() } @@ -94,10 +94,19 @@ struct AppSearchView: View { Button("") { withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) { - selectedSortAlpha.toggle() + // Cycle through all enum cases using `CaseIterable` + if let nextSortOption = SortOption(rawValue: selectedSortOption.rawValue + 1) { + selectedSortOption = nextSortOption + showPopover = false + showMenu = false + } else { + selectedSortOption = .alphabetical + showPopover = false + showMenu = false + } } } - .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Sorting: \(selectedSortAlpha ? "Name" : "Size")", help: "Sort app list alphabetically by name or by size", size: 5)) + .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Sorting: \(selectedSortOption.title)", help: "Sort app list alphabetically by name or by size", size: 5)) if mini && !menubarEnabled { Button("") { @@ -149,15 +158,48 @@ struct AppSearchView: View { Button("Quit") { NSApp.terminate(nil) } - .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Quit Pearcleaner", help: "Quit Pearcleaner", size: 5)) + .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Quit", help: "Quit Pearcleaner", size: 5)) } } .padding() .background(backgroundView(themeManager: themeManager, glass: glass).padding(-80)) - .frame(width: 150) +// .frame(width: 200) + + } + } else if search.isEmpty && (!mini || !menubarEnabled) { + + Button("") { + withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) { + showMenu.toggle() + } + } + .buttonStyle(SimpleButtonStyle(icon: "line.3.horizontal.decrease.circle", help: selectedSortOption.title, size: 16)) + .popover(isPresented: $showMenu, arrowEdge: .bottom) { + VStack(alignment: .leading, spacing: 10) { + HStack { + Spacer() + Text("Sorting Options").font(.subheadline).foregroundStyle(.secondary) + Spacer() + } + Divider() + ForEach(SortOption.allCases) { option in + Button("") { + withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) { + selectedSortOption = option + showMenu = false + } + } + .buttonStyle(SimpleButtonStyle(icon: selectedSortOption == option ? "circle.inset.filled" : "circle", label: option.title, help: "", size: 5)) + + } + } + .padding() + .background(backgroundView(themeManager: themeManager, glass: glass).padding(-80)) +// .frame(width: 160) } + } @@ -174,12 +216,28 @@ struct AppSearchView: View { apps = appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) } } - switch selectedSortAlpha { - case true: - return apps.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() } - case false: + // Sort based on the selected option + switch selectedSortOption { + case .alphabetical: + return apps.sorted { + $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() + } + case .size: return apps.sorted { $0.bundleSize > $1.bundleSize } + case .creationDate: + return apps.sorted { ($0.creationDate ?? Date.distantPast) > ($1.creationDate ?? Date.distantPast) } + case .contentChangeDate: + return apps.sorted { ($0.contentChangeDate ?? Date.distantPast) > ($1.contentChangeDate ?? Date.distantPast) } + case .lastUsedDate: + return apps.sorted { ($0.lastUsedDate ?? Date.distantPast) > ($1.lastUsedDate ?? Date.distantPast) } } + +// switch selectedSortAlpha { +// case true: +// return apps.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() } +// case false: +// return apps.sorted { $0.bundleSize > $1.bundleSize } +// } } } @@ -218,7 +276,7 @@ struct SimpleSearchStyle: TextFieldStyle { Spacer() Text(isFocused ? "Type to search" : "Click to search") .font(.subheadline) - .foregroundColor(.primary.opacity(0.2)) + .foregroundColor(.secondary) Spacer() } } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index 97fd883..68c62d7 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -22,6 +22,7 @@ struct FilesView: View { @AppStorage("settings.general.filesWarning") private var warning: Bool = false @AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true @AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false + @AppStorage("settings.interface.details") private var detailsEnabled: Bool = true @State private var showAlert = false @Environment(\.colorScheme) var colorScheme @Binding var showPopover: Bool @@ -30,7 +31,7 @@ struct FilesView: View { var body: some View { - var totalSelectedSize: (real: String, logical: String, finder: String) { + var totalSelectedSize: (real: String, logical: String) { var totalReal: Int64 = 0 var totalLogical: Int64 = 0 for url in appState.selectedItems { @@ -39,12 +40,11 @@ struct FilesView: View { totalReal += realSize totalLogical += logicalSize } - return (real: formatByte(size: totalReal).human, logical: formatByte(size: totalLogical).human, finder: "\(formatByte(size: totalLogical).byte) (\(formatByte(size: totalReal).human))") + return (real: formatByte(size: totalReal).human, logical: formatByte(size: totalLogical).human) } let displaySizeTotal = sizeType == "Real" ? formatByte(size: appState.appInfo.totalSize).human : - sizeType == "Logical" ? formatByte(size: appState.appInfo.totalSizeLogical).human : - "\(formatByte(size: appState.appInfo.totalSizeLogical).byte) (\(formatByte(size: appState.appInfo.totalSize).human))" + formatByte(size: appState.appInfo.totalSizeLogical).human VStack(alignment: .center) { if appState.showProgress { @@ -91,65 +91,112 @@ struct FilesView: View { HStack(alignment: .center) { //app icon, title, size and items - VStack(alignment: .center) { - HStack(alignment: .center) { - if let appIcon = appState.appInfo.appIcon { - Image(nsImage: appIcon) - .resizable() - .scaledToFit() - .frame(width: 50, height: 50) - .padding() - .background{ - RoundedRectangle(cornerRadius: 16) - .fill(Color((appState.appInfo.appIcon?.averageColor)!)) + VStack(alignment: .leading, spacing: 5){ + PearGroupBox(header: { + HStack(alignment: .center, spacing: 15) { + if let appIcon = appState.appInfo.appIcon { + Image(nsImage: appIcon) + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .padding(5) + .background{ + RoundedRectangle(cornerRadius: 10) + .fill(Color((appState.appInfo.appIcon?.averageColor)!)) - } - } - - VStack(alignment: .leading, spacing: 5){ - HStack { - Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold).lineLimit(1) - Text("•").foregroundStyle(Color("AccentColor")) - Text("v\(appState.appInfo.appVersion)").font(.title3) - - } - Text("\(appState.appInfo.bundleIdentifier)") - .lineLimit(1) - .font(.title3) - .foregroundStyle((.primary.opacity(0.5))) - - if let creationDate = appState.appInfo.creationDate { - Text("Installed: \(formattedMDDate(from: creationDate))") - .font(.footnote) + } } - if let modificationDate = appState.appInfo.contentChangeDate { - Text("Modified: \(formattedMDDate(from: modificationDate))") - .font(.footnote) + VStack(alignment: .leading) { + HStack(alignment: .center) { + Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold).lineLimit(1) + Image(systemName: "circle.fill").foregroundStyle(Color("AccentColor")).font(.system(size: 5)) + Text("\(appState.appInfo.appVersion)").font(.title3) + + } + Text("\(appState.appInfo.bundleIdentifier)") + .lineLimit(1) + .font(.title3) + .foregroundStyle((.primary.opacity(0.5))) } - if let lastUsedDate = appState.appInfo.lastUsedDate { - Text("Last Used: \(formattedMDDate(from: lastUsedDate))") - .font(.footnote) + Spacer() + + Button("\(detailsEnabled ? "Hide Details" : "Show Details")") { + withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) { + detailsEnabled.toggle() + } } } - .padding(.leading) - Spacer() + }, content: { + + if detailsEnabled { + HStack(spacing: 20) { + VStack(alignment: .leading, spacing: 5) { + Text("Total size of files:") + .font(.callout).fontWeight(.bold) + Text("") + .font(.footnote) + Text("Installed Date:") + .font(.footnote) + Text("Modified Date:") + .font(.footnote) + Text("Last Used Date:") + .font(.footnote) + + } + Spacer() + VStack(alignment: .trailing, spacing: 5) { + Text("\(displaySizeTotal)").font(.callout).fontWeight(.bold)//.help("Total size on disk") + + Text("\(appState.appInfo.fileSize.count == 1 ? "\(appState.selectedItems.count) of \(appState.appInfo.fileSize.count) item" : "\(appState.selectedItems.count) of \(appState.appInfo.fileSize.count) items")") + .font(.footnote) + + if let creationDate = appState.appInfo.creationDate { + Text(formattedMDDate(from: creationDate)) + .font(.footnote) + .foregroundStyle(.secondary) + } else { + Text("Not available") + .font(.footnote) + .foregroundStyle(.secondary) + } + + if let modificationDate = appState.appInfo.contentChangeDate { + Text(formattedMDDate(from: modificationDate)) + .font(.footnote) + .foregroundStyle(.secondary) + } else { + Text("Not available") + .font(.footnote) + .foregroundStyle(.secondary) + } + + if let lastUsedDate = appState.appInfo.lastUsedDate { + Text(formattedMDDate(from: lastUsedDate)) + .font(.footnote) + .foregroundStyle(.secondary) + } else { + Text("Not available") + .font(.footnote) + .foregroundStyle(.secondary) + } + - VStack(alignment: .trailing, spacing: 5) { - Text("\(displaySizeTotal)").font(.title).fontWeight(.bold).help("Total size on disk") - Text("\(appState.appInfo.fileSize.count == 1 ? "\(appState.selectedItems.count) / \(appState.appInfo.fileSize.count) item" : "\(appState.selectedItems.count) / \(appState.appInfo.fileSize.count) items")") - .font(.callout).foregroundStyle((.primary.opacity(0.5))) + } + } } - } + }) + } .padding(.horizontal, 20) .padding(.top, 0) + } @@ -166,7 +213,6 @@ struct FilesView: View { .toggleStyle(SimpleCheckboxToggleStyle()) .help("All checkboxes") - Spacer() @@ -228,7 +274,7 @@ struct FilesView: View { Button("") { selectedSortAlpha.toggle() } - .buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size")) + .buttonStyle(SimpleButtonStyle(icon: "line.3.horizontal.decrease.circle", label: selectedSortAlpha ? "Name" : "Size", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size", size: 16)) } .padding() @@ -290,11 +336,11 @@ struct FilesView: View { Spacer() - InfoButton(text: "Always double-check the files/folders marked for removal. In some rare cases, Pearcleaner may find some unrelated files when app names are too similar.", color: .primary.opacity(0.5), warning: true, edge: .top) - .padding(.top) +// InfoButton(text: "Always double-check the files/folders marked for removal. In some rare cases, Pearcleaner may find some unrelated files when app names are too similar.", color: .primary.opacity(0.5), warning: true, edge: .top) +// .padding(.top) - Button("\(sizeType == "Logical" ? totalSelectedSize.logical : sizeType == "Finder" ? totalSelectedSize.finder : totalSelectedSize.real)") { + Button("\(sizeType == "Logical" ? totalSelectedSize.logical : totalSelectedSize.real)") { showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: { Task { @@ -377,7 +423,7 @@ struct FilesView: View { } .buttonStyle(UninstallButton(isEnabled: !appState.selectedItems.isEmpty)) .disabled(appState.selectedItems.isEmpty) - .padding(.top) + .padding(.top, 5) } @@ -509,9 +555,7 @@ struct FileDetailsItem: View { Spacer() let displaySize = sizeType == "Real" ? formatByte(size: size!).human : - sizeType == "Logical" ? formatByte(size: sizeL!).human : - "\(formatByte(size: sizeL!).byte) (\(formatByte(size: size!).human))" - + formatByte(size: sizeL!).human Text("\(displaySize)") } diff --git a/Pearcleaner/Views/RegularMode.swift b/Pearcleaner/Views/RegularMode.swift index 7b5afb4..9562e57 100644 --- a/Pearcleaner/Views/RegularMode.swift +++ b/Pearcleaner/Views/RegularMode.swift @@ -114,10 +114,10 @@ struct RegularMode: View { .buttonStyle(.borderless) .padding(2) .padding(.vertical, 2) - .background { - RoundedRectangle(cornerRadius: 8) - .strokeBorder(.secondary.opacity(0.5), lineWidth: 1) - } +// .background { +// RoundedRectangle(cornerRadius: 8) +// .strokeBorder(.secondary.opacity(0.5), lineWidth: 1) +// } } .padding(6) @@ -126,7 +126,7 @@ struct RegularMode: View { } } - .frame(minWidth: 900, minHeight: 600) + .frame(minWidth: appState.currentPage == .orphans ? 700 : 900, minHeight: 600) .edgesIgnoringSafeArea(.all) diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index e51b7d9..695f0d7 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -35,7 +35,6 @@ struct ZombieView: View { @State private var totalLogicalSize: Int64 = 0 @State private var totalRealSizeUninstallBtn: String = "" @State private var totalLogicalSizeUninstallBtn: String = "" - @State private var totalFinderSizeUninstallBtn: String = "" var body: some View { @@ -80,39 +79,45 @@ struct ZombieView: View { .padding(.trailing, (mini || menubarEnabled) ? 6 : 0) - VStack() { + VStack(spacing: 0) { + // Main Group - HStack() { + HStack(alignment: .center) { - VStack(alignment: .center) { + VStack(alignment: .leading, spacing: 5){ - HStack(alignment: .center) { - Image(systemName: "doc.badge.clock.fill") - .resizable() - .scaledToFit() - .frame(width: 50, height: 50) - .symbolRenderingMode(.hierarchical) - .padding(.trailing) + PearGroupBox(header: { + HStack(alignment: .center, spacing: 15) { + Image(systemName: "doc.badge.clock.fill") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .symbolRenderingMode(.hierarchical) - VStack(alignment: .leading, spacing: 10){ - HStack { + VStack(alignment: .leading){ Text("Orphaned Files").font(.title).fontWeight(.bold) - Spacer() + Text("Remaining files and folders from previous applications") + .font(.callout).foregroundStyle(.primary.opacity(0.5)) } - Text("Remaining files and folders from previous applications") - .font(.callout).foregroundStyle(.primary.opacity(0.5)) } + }, content: { + HStack(spacing: 20) { + VStack(alignment: .leading, spacing: 5) { + Text("Total size of files:") + .font(.callout).fontWeight(.bold) + Text("") + .font(.footnote) + } + Spacer() + VStack(alignment: .trailing, spacing: 5) { + Text("\(displaySizeTotal)").font(.callout).fontWeight(.bold)//.help("Total size on disk") - Spacer() - - VStack(alignment: .trailing, spacing: 5) { - Text("\(displaySizeTotal)").font(.title).fontWeight(.bold).help("Total size on disk") + Text("\(selectedZombieItemsLocal.count) of \(searchZ.isEmpty ? appState.zombieFile.fileSize.count : memoizedFiles.count) \(appState.zombieFile.fileSize.count == 1 ? "item" : "items")") + .font(.footnote).foregroundStyle(.secondary) + } - Text("\(selectedZombieItemsLocal.count) / \(searchZ.isEmpty ? appState.zombieFile.fileSize.count : memoizedFiles.count) \(appState.zombieFile.fileSize.count == 1 ? "item" : "items")") - .font(.callout).foregroundStyle(.primary.opacity(0.5)) } - - } + }) } .padding(.horizontal, 20) @@ -157,7 +162,6 @@ struct ZombieView: View { .toggleStyle(SimpleCheckboxToggleStyle()) .help("All checkboxes") - SearchBar(search: $searchZ, darker: true, glass: glass, sidebar: false) .padding(.horizontal) .onChange(of: searchZ) { newValue in @@ -170,7 +174,7 @@ struct ZombieView: View { selectedSortAlpha.toggle() updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true) } - .buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size")) + .buttonStyle(SimpleButtonStyle(icon: "line.3.horizontal.decrease.circle", label: selectedSortAlpha ? "Name" : "Size", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size", size: 16)) } @@ -203,7 +207,7 @@ struct ZombieView: View { Spacer() - InfoButton(text: "Leftover file search is not 100% accurate as it doesn't have any uninstalled app bundles to check against for file exclusion. This does a best guess search for files/folders and excludes the ones that have overlap with your currently installed applications. Please confirm files marked for deletion really do belong to uninstalled applications.", color: .orange, warning: true, edge: .top) +// InfoButton(text: "Leftover file search is not 100% accurate as it doesn't have any uninstalled app bundles to check against for file exclusion. This does a best guess search for files/folders and excludes the ones that have overlap with your currently installed applications. Please confirm files marked for deletion really do belong to uninstalled applications.", color: .orange, warning: true, edge: .top) Button("Rescan") { updateOnMain { @@ -214,7 +218,7 @@ struct ZombieView: View { } .buttonStyle(RescanButton()) - Button("\(sizeType == "Logical" ? totalLogicalSizeUninstallBtn : sizeType == "Finder" ? totalFinderSizeUninstallBtn : totalRealSizeUninstallBtn)") { + Button("\(sizeType == "Logical" ? totalLogicalSizeUninstallBtn : totalRealSizeUninstallBtn)") { showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: { Task { @@ -342,7 +346,7 @@ struct ZombieView: View { let filteredFilesReal = fileSizeReal.filter { url, _ in searchTerm.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchTerm) } let filteredFilesLogical = fileSizeLogical.filter { url, _ in searchTerm.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchTerm) } - let filesToSort = sizeType == "Real" || sizeType == "Finder" ? filteredFilesReal : filteredFilesLogical + let filesToSort = sizeType == "Real" ? filteredFilesReal : filteredFilesLogical let sortedFilteredFiles = filesToSort.sorted { (left, right) -> Bool in if selectedSortAlpha { return left.key.lastPathComponent.pearFormat() < right.key.lastPathComponent.pearFormat() @@ -377,15 +381,14 @@ struct ZombieView: View { let sizes = calculateTotalSelectedZombieSize() totalRealSizeUninstallBtn = sizes.real totalLogicalSizeUninstallBtn = sizes.logical - totalFinderSizeUninstallBtn = "\(sizes.logical) (\(sizes.real))" } private var displaySizeText: String { switch sizeType { case "Logical": return totalLogicalSizeUninstallBtn - case "Finder": - return totalFinderSizeUninstallBtn + case "Real": + return totalRealSizeUninstallBtn default: return totalRealSizeUninstallBtn } @@ -481,8 +484,7 @@ struct ZombieFileDetailsItem: View { Spacer() let displaySize = sizeType == "Real" ? formatByte(size: size!).human : - sizeType == "Logical" ? formatByte(size: sizeL!).human : - "\(formatByte(size: sizeL!).byte) (\(formatByte(size: size!).human))" + formatByte(size: sizeL!).human Text("\(displaySize)")