This commit is contained in:
Alin
2024-10-07 15:15:09 -06:00
parent b4d676d8a4
commit 7f1aaf4650
9 changed files with 257 additions and 136 deletions
+4 -4
View File
@@ -537,8 +537,8 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 62; APP_BUILD = 63;
APP_VERSION = 3.9.0; APP_VERSION = 3.9.1;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -607,8 +607,8 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
APP_BUILD = 62; APP_BUILD = 63;
APP_VERSION = 3.9.0; APP_VERSION = 3.9.1;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+3 -2
View File
@@ -14,7 +14,8 @@ import AlinFoundation
class MetadataAppInfoFetcher { class MetadataAppInfoFetcher {
static func getAppInfo(fromMetadata metadata: [String: Any], atPath path: URL) -> AppInfo? { static func getAppInfo(fromMetadata metadata: [String: Any], atPath path: URL) -> AppInfo? {
// Extract metadata attributes for known fields // 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 fsName = metadata["kMDItemFSName"] as? String ?? path.lastPathComponent
let appName = displayName.isEmpty ? fsName : displayName let appName = displayName.isEmpty ? fsName : displayName
@@ -152,7 +153,7 @@ class AppInfoUtils {
static func fetchAppIcon(for path: URL, wrapped: Bool, md: Bool = false) -> NSImage? { static func fetchAppIcon(for path: URL, wrapped: Bool, md: Bool = false) -> NSImage? {
let iconPath = wrapped ? (md ? path : path.deletingLastPathComponent().deletingLastPathComponent()) : path let iconPath = wrapped ? (md ? path : path.deletingLastPathComponent().deletingLastPathComponent()) : path
if let appIcon = getIconForFileOrFolderNS(atPath: iconPath) { 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 { } else {
printOS("App Icon not found for app at path: \(path)") printOS("App Icon not found for app at path: \(path)")
return nil return nil
+15
View File
@@ -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 enum CurrentTabView:Int
{ {
+1
View File
@@ -186,6 +186,7 @@ struct Sponsor: Identifiable {
let url: URL let url: URL
static let sponsors: [Sponsor] = [ 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: "chris3ware", url: URL(string: "https://github.com/chris3ware")!),
Sponsor(name: "fpuhan", url: URL(string: "https://github.com/fpuhan")!), Sponsor(name: "fpuhan", url: URL(string: "https://github.com/fpuhan")!),
Sponsor(name: "HungThinhIT", url: URL(string: "https://github.com/HungThinhIT")!), Sponsor(name: "HungThinhIT", url: URL(string: "https://github.com/HungThinhIT")!),
+25 -25
View File
@@ -19,7 +19,7 @@ struct GeneralSettingsTab: View {
@AppStorage("settings.general.brew") private var brew: Bool = false @AppStorage("settings.general.brew") private var brew: Bool = false
@AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false @AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false
@AppStorage("settings.general.confirmAlert") private var confirmAlert: 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" @AppStorage("settings.general.sizeType") var sizeType: String = "Real"
@State private var isCLISymlinked = false @State private var isCLISymlinked = false
@@ -107,28 +107,28 @@ struct GeneralSettingsTab: View {
HStack(spacing: 0) { // HStack(spacing: 0) {
Image(systemName: selectedSortAlpha ? "textformat.abc" : "textformat.123") // Image(systemName: selectedSortAlpha ? "textformat.abc" : "textformat.123")
.resizable() // .resizable()
.scaledToFit() // .scaledToFit()
.frame(width: 20, height: 20) // .frame(width: 20, height: 20)
.padding(.trailing) // .padding(.trailing)
.foregroundStyle(.primary) // .foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) { // VStack(alignment: .leading, spacing: 5) {
Text("File list sorting order") // Text("File list sorting order")
.font(.callout) // .font(.callout)
.foregroundStyle(.primary) // .foregroundStyle(.primary)
} // }
Spacer() // Spacer()
Picker("", selection: $selectedSortAlpha) { // Picker("", selection: $selectedSortAlpha) {
Text("Alphabetical") // Text("Alphabetical")
.tag(true) // .tag(true)
Text("File Size") // Text("File Size")
.tag(false) // .tag(false)
} // }
.buttonStyle(.borderless) // .buttonStyle(.borderless)
} // }
.padding(5) // .padding(5)
@@ -151,8 +151,8 @@ struct GeneralSettingsTab: View {
.tag("Real") .tag("Real")
Text("Logical") Text("Logical")
.tag("Logical") .tag("Logical")
Text("Finder") // Text("Finder")
.tag("Finder") // .tag("Finder")
} }
.buttonStyle(.borderless) .buttonStyle(.borderless)
+71 -13
View File
@@ -22,7 +22,7 @@ struct AppSearchView: View {
@Binding var showPopover: Bool @Binding var showPopover: Bool
@State private var showMenu = false @State private var showMenu = false
@Binding var isMenuBar: Bool @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 @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)) .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") { Button("More") {
self.showMenu.toggle() self.showMenu.toggle()
} }
@@ -94,10 +94,19 @@ struct AppSearchView: View {
Button("") { Button("") {
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) { 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 { if mini && !menubarEnabled {
Button("") { Button("") {
@@ -149,15 +158,48 @@ struct AppSearchView: View {
Button("Quit") { Button("Quit") {
NSApp.terminate(nil) 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() .padding()
.background(backgroundView(themeManager: themeManager, glass: glass).padding(-80)) .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) } apps = appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
} }
switch selectedSortAlpha { // Sort based on the selected option
case true: switch selectedSortOption {
return apps.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() } case .alphabetical:
case false: return apps.sorted {
return apps.sorted { $0.bundleSize > $1.bundleSize } $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() Spacer()
Text(isFocused ? "Type to search" : "Click to search") Text(isFocused ? "Type to search" : "Click to search")
.font(.subheadline) .font(.subheadline)
.foregroundColor(.primary.opacity(0.2)) .foregroundColor(.secondary)
Spacer() Spacer()
} }
} }
+84 -40
View File
@@ -22,6 +22,7 @@ struct FilesView: View {
@AppStorage("settings.general.filesWarning") private var warning: Bool = false @AppStorage("settings.general.filesWarning") private var warning: Bool = false
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true @AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false @AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
@AppStorage("settings.interface.details") private var detailsEnabled: Bool = true
@State private var showAlert = false @State private var showAlert = false
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@Binding var showPopover: Bool @Binding var showPopover: Bool
@@ -30,7 +31,7 @@ struct FilesView: View {
var body: some View { var body: some View {
var totalSelectedSize: (real: String, logical: String, finder: String) { var totalSelectedSize: (real: String, logical: String) {
var totalReal: Int64 = 0 var totalReal: Int64 = 0
var totalLogical: Int64 = 0 var totalLogical: Int64 = 0
for url in appState.selectedItems { for url in appState.selectedItems {
@@ -39,12 +40,11 @@ struct FilesView: View {
totalReal += realSize totalReal += realSize
totalLogical += logicalSize 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 : let displaySizeTotal = sizeType == "Real" ? formatByte(size: appState.appInfo.totalSize).human :
sizeType == "Logical" ? formatByte(size: appState.appInfo.totalSizeLogical).human : formatByte(size: appState.appInfo.totalSizeLogical).human
"\(formatByte(size: appState.appInfo.totalSizeLogical).byte) (\(formatByte(size: appState.appInfo.totalSize).human))"
VStack(alignment: .center) { VStack(alignment: .center) {
if appState.showProgress { if appState.showProgress {
@@ -91,55 +91,96 @@ struct FilesView: View {
HStack(alignment: .center) { HStack(alignment: .center) {
//app icon, title, size and items //app icon, title, size and items
VStack(alignment: .center) { VStack(alignment: .leading, spacing: 5){
HStack(alignment: .center) { PearGroupBox(header: {
HStack(alignment: .center, spacing: 15) {
if let appIcon = appState.appInfo.appIcon { if let appIcon = appState.appInfo.appIcon {
Image(nsImage: appIcon) Image(nsImage: appIcon)
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 50, height: 50) .frame(width: 50, height: 50)
.padding() .padding(5)
.background{ .background{
RoundedRectangle(cornerRadius: 16) RoundedRectangle(cornerRadius: 10)
.fill(Color((appState.appInfo.appIcon?.averageColor)!)) .fill(Color((appState.appInfo.appIcon?.averageColor)!))
} }
} }
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 5){ HStack(alignment: .center) {
HStack {
Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold).lineLimit(1) Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold).lineLimit(1)
Text("").foregroundStyle(Color("AccentColor")) Image(systemName: "circle.fill").foregroundStyle(Color("AccentColor")).font(.system(size: 5))
Text("v\(appState.appInfo.appVersion)").font(.title3) Text("\(appState.appInfo.appVersion)").font(.title3)
} }
Text("\(appState.appInfo.bundleIdentifier)") Text("\(appState.appInfo.bundleIdentifier)")
.lineLimit(1) .lineLimit(1)
.font(.title3) .font(.title3)
.foregroundStyle((.primary.opacity(0.5))) .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)
}
if let lastUsedDate = appState.appInfo.lastUsedDate {
Text("Last Used: \(formattedMDDate(from: lastUsedDate))")
.font(.footnote)
}
}
.padding(.leading)
Spacer() Spacer()
Button("\(detailsEnabled ? "Hide Details" : "Show Details")") {
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
detailsEnabled.toggle()
}
}
}
}, 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) { 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)
}
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)))
} }
@@ -147,9 +188,15 @@ struct FilesView: View {
} }
} }
})
}
.padding(.horizontal, 20) .padding(.horizontal, 20)
.padding(.top, 0) .padding(.top, 0)
} }
@@ -166,7 +213,6 @@ struct FilesView: View {
.toggleStyle(SimpleCheckboxToggleStyle()) .toggleStyle(SimpleCheckboxToggleStyle())
.help("All checkboxes") .help("All checkboxes")
Spacer() Spacer()
@@ -228,7 +274,7 @@ struct FilesView: View {
Button("") { Button("") {
selectedSortAlpha.toggle() 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() .padding()
@@ -290,11 +336,11 @@ struct FilesView: View {
Spacer() 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) // 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) // .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: { showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: {
Task { Task {
@@ -377,7 +423,7 @@ struct FilesView: View {
} }
.buttonStyle(UninstallButton(isEnabled: !appState.selectedItems.isEmpty)) .buttonStyle(UninstallButton(isEnabled: !appState.selectedItems.isEmpty))
.disabled(appState.selectedItems.isEmpty) .disabled(appState.selectedItems.isEmpty)
.padding(.top) .padding(.top, 5)
} }
@@ -509,9 +555,7 @@ struct FileDetailsItem: View {
Spacer() Spacer()
let displaySize = sizeType == "Real" ? formatByte(size: size!).human : let displaySize = sizeType == "Real" ? formatByte(size: size!).human :
sizeType == "Logical" ? formatByte(size: sizeL!).human : formatByte(size: sizeL!).human
"\(formatByte(size: sizeL!).byte) (\(formatByte(size: size!).human))"
Text("\(displaySize)") Text("\(displaySize)")
} }
+5 -5
View File
@@ -114,10 +114,10 @@ struct RegularMode: View {
.buttonStyle(.borderless) .buttonStyle(.borderless)
.padding(2) .padding(2)
.padding(.vertical, 2) .padding(.vertical, 2)
.background { // .background {
RoundedRectangle(cornerRadius: 8) // RoundedRectangle(cornerRadius: 8)
.strokeBorder(.secondary.opacity(0.5), lineWidth: 1) // .strokeBorder(.secondary.opacity(0.5), lineWidth: 1)
} // }
} }
.padding(6) .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) .edgesIgnoringSafeArea(.all)
+28 -26
View File
@@ -35,7 +35,6 @@ struct ZombieView: View {
@State private var totalLogicalSize: Int64 = 0 @State private var totalLogicalSize: Int64 = 0
@State private var totalRealSizeUninstallBtn: String = "" @State private var totalRealSizeUninstallBtn: String = ""
@State private var totalLogicalSizeUninstallBtn: String = "" @State private var totalLogicalSizeUninstallBtn: String = ""
@State private var totalFinderSizeUninstallBtn: String = ""
var body: some View { var body: some View {
@@ -80,39 +79,45 @@ struct ZombieView: View {
.padding(.trailing, (mini || menubarEnabled) ? 6 : 0) .padding(.trailing, (mini || menubarEnabled) ? 6 : 0)
VStack() { VStack(spacing: 0) {
// Main Group // Main Group
HStack() {
VStack(alignment: .center) {
HStack(alignment: .center) { HStack(alignment: .center) {
VStack(alignment: .leading, spacing: 5){
PearGroupBox(header: {
HStack(alignment: .center, spacing: 15) {
Image(systemName: "doc.badge.clock.fill") Image(systemName: "doc.badge.clock.fill")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 50, height: 50) .frame(width: 50, height: 50)
.symbolRenderingMode(.hierarchical) .symbolRenderingMode(.hierarchical)
.padding(.trailing)
VStack(alignment: .leading, spacing: 10){ VStack(alignment: .leading){
HStack {
Text("Orphaned Files").font(.title).fontWeight(.bold) Text("Orphaned Files").font(.title).fontWeight(.bold)
Spacer()
}
Text("Remaining files and folders from previous applications") Text("Remaining files and folders from previous applications")
.font(.callout).foregroundStyle(.primary.opacity(0.5)) .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() Spacer()
VStack(alignment: .trailing, spacing: 5) { VStack(alignment: .trailing, spacing: 5) {
Text("\(displaySizeTotal)").font(.title).fontWeight(.bold).help("Total size on disk") Text("\(displaySizeTotal)").font(.callout).fontWeight(.bold)//.help("Total size on disk")
Text("\(selectedZombieItemsLocal.count) / \(searchZ.isEmpty ? appState.zombieFile.fileSize.count : memoizedFiles.count) \(appState.zombieFile.fileSize.count == 1 ? "item" : "items")") Text("\(selectedZombieItemsLocal.count) of \(searchZ.isEmpty ? appState.zombieFile.fileSize.count : memoizedFiles.count) \(appState.zombieFile.fileSize.count == 1 ? "item" : "items")")
.font(.callout).foregroundStyle(.primary.opacity(0.5)) .font(.footnote).foregroundStyle(.secondary)
} }
} }
})
} }
.padding(.horizontal, 20) .padding(.horizontal, 20)
@@ -157,7 +162,6 @@ struct ZombieView: View {
.toggleStyle(SimpleCheckboxToggleStyle()) .toggleStyle(SimpleCheckboxToggleStyle())
.help("All checkboxes") .help("All checkboxes")
SearchBar(search: $searchZ, darker: true, glass: glass, sidebar: false) SearchBar(search: $searchZ, darker: true, glass: glass, sidebar: false)
.padding(.horizontal) .padding(.horizontal)
.onChange(of: searchZ) { newValue in .onChange(of: searchZ) { newValue in
@@ -170,7 +174,7 @@ struct ZombieView: View {
selectedSortAlpha.toggle() selectedSortAlpha.toggle()
updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true) 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() 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") { Button("Rescan") {
updateOnMain { updateOnMain {
@@ -214,7 +218,7 @@ struct ZombieView: View {
} }
.buttonStyle(RescanButton()) .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: { showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: {
Task { Task {
@@ -342,7 +346,7 @@ struct ZombieView: View {
let filteredFilesReal = fileSizeReal.filter { url, _ in searchTerm.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchTerm) } 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 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 let sortedFilteredFiles = filesToSort.sorted { (left, right) -> Bool in
if selectedSortAlpha { if selectedSortAlpha {
return left.key.lastPathComponent.pearFormat() < right.key.lastPathComponent.pearFormat() return left.key.lastPathComponent.pearFormat() < right.key.lastPathComponent.pearFormat()
@@ -377,15 +381,14 @@ struct ZombieView: View {
let sizes = calculateTotalSelectedZombieSize() let sizes = calculateTotalSelectedZombieSize()
totalRealSizeUninstallBtn = sizes.real totalRealSizeUninstallBtn = sizes.real
totalLogicalSizeUninstallBtn = sizes.logical totalLogicalSizeUninstallBtn = sizes.logical
totalFinderSizeUninstallBtn = "\(sizes.logical) (\(sizes.real))"
} }
private var displaySizeText: String { private var displaySizeText: String {
switch sizeType { switch sizeType {
case "Logical": case "Logical":
return totalLogicalSizeUninstallBtn return totalLogicalSizeUninstallBtn
case "Finder": case "Real":
return totalFinderSizeUninstallBtn return totalRealSizeUninstallBtn
default: default:
return totalRealSizeUninstallBtn return totalRealSizeUninstallBtn
} }
@@ -481,8 +484,7 @@ struct ZombieFileDetailsItem: View {
Spacer() Spacer()
let displaySize = sizeType == "Real" ? formatByte(size: size!).human : let displaySize = sizeType == "Real" ? formatByte(size: size!).human :
sizeType == "Logical" ? formatByte(size: sizeL!).human : formatByte(size: sizeL!).human
"\(formatByte(size: sizeL!).byte) (\(formatByte(size: size!).human))"
Text("\(displaySize)") Text("\(displaySize)")