diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index b879cc7..74838da 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -575,7 +575,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 30; + CURRENT_PROJECT_VERSION = 31; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -593,7 +593,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.3.4; + MARKETING_VERSION = 3.4.0; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -610,7 +610,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 30; + CURRENT_PROJECT_VERSION = 31; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -628,7 +628,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.3.4; + MARKETING_VERSION = 3.4.0; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index ff17f81..c433945 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -186,4 +186,3 @@ enum DisplayMode: Int, CaseIterable { } - diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index 8d4efee..63d34c1 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -123,6 +123,44 @@ struct InfoButton: View { } +struct UninstallButton: ButtonStyle { + var isEnabled: Bool + + func makeBody(configuration: Configuration) -> some View { + HStack(alignment: .center, spacing: 10) { + Image(systemName: "trash") + .foregroundColor(isEnabled ? .white.opacity(1) : .white.opacity(0.3)) + + Divider() + .frame(height: 24) + .opacity(0.5) + + configuration.label + .foregroundColor(isEnabled ? .white.opacity(1) : .white.opacity(0.3)) + + } + .frame(height: 24) + .frame(minWidth: 75) + .padding(.horizontal) + .padding(.vertical, 4) + .background(configuration.isPressed ? Color("uninstall").opacity(0.8) : Color("uninstall")) +// .background( +// configuration.isPressed ? Color("uninstall").opacity(0.8) : +// (isEnabled ? Color("uninstall") : Color.gray) +// ) + .cornerRadius(8) + .onHover { over in + if over { + NSCursor.pointingHand.push() + } else { + NSCursor.pop() + } + } + } +} + + + struct WarningPopoverView: View { var label: String var bodyText: String diff --git a/Pearcleaner/Resources/Assets.xcassets/uninstall.colorset/Contents.json b/Pearcleaner/Resources/Assets.xcassets/uninstall.colorset/Contents.json new file mode 100644 index 0000000..ed7fbd2 --- /dev/null +++ b/Pearcleaner/Resources/Assets.xcassets/uninstall.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x4D", + "green" : "0x58", + "red" : "0xD7" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x3E", + "green" : "0x47", + "red" : "0xB0" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index 1ae6d1f..47cc871 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -24,6 +24,8 @@ struct GeneralSettingsTab: View { @AppStorage("settings.general.brew") private var brew: Bool = false @AppStorage("settings.general.instant") private var instantSearch: Bool = true @AppStorage("settings.general.selectedTheme") var selectedTheme: String = "Auto" + @AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true + @State private var diskStatus: Bool = false @State private var accessStatus: Bool = false private let themes = ["Auto", "Dark", "Light"] @@ -89,6 +91,53 @@ struct GeneralSettingsTab: View { .padding(.leading) + HStack(spacing: 0) { + Image(systemName: selectedSortAlpha ? "textformat.abc" : "textformat.123") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .padding(.trailing) + .foregroundStyle(.gray) + VStack(alignment: .leading, spacing: 5) { + Text("File list sorting mode") + .font(.callout) + .foregroundStyle(.gray) + } + InfoButton(text: "When searching for app files or leftover files, the list will be sorted based on this choice", color: nil, label: "") + Spacer() + Picker("", selection: $selectedSortAlpha) { + Text("Alphabetical").tag(true) + Text("Size").tag(false) + } + .pickerStyle(SegmentedPickerStyle()) + .frame(width: 200) + } + .padding(5) + .padding(.leading) +// HStack(spacing: 0) { +// Image(systemName: selectedSortAlpha ? "textformat.abc" : "textformat.123") +// .resizable() +// .scaledToFit() +// .frame(width: 20, height: 20) +// .padding(.trailing) +// .foregroundStyle(.gray) +// VStack(alignment: .leading, spacing: 5) { +// Text("\(selectedSortAlpha ? "File list sorted alphabetically" : "File list sorted by size")") +// .font(.callout) +// .foregroundStyle(.gray) +// } +// +// InfoButton(text: "When searching for app files or leftover files, the list will be sorted based on this choice", color: nil, label: "") +// +// Spacer() +// Toggle(isOn: $selectedSortAlpha, label: { +// }) +// .toggleStyle(.switch) +// } +// .padding(5) +// .padding(.leading) + + // === Perms ================================================================================================ @@ -208,7 +257,7 @@ struct GeneralSettingsTab: View { } .padding(20) - .frame(width: 500, height: 420) + .frame(width: 500, height: 460) } diff --git a/Pearcleaner/Settings/Interface.swift b/Pearcleaner/Settings/Interface.swift index 03f420d..0830cf8 100644 --- a/Pearcleaner/Settings/Interface.swift +++ b/Pearcleaner/Settings/Interface.swift @@ -18,18 +18,18 @@ struct InterfaceSettingsTab: View { @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("displayMode") var displayMode: DisplayMode = .system + @AppStorage("settings.general.selectedTab") private var selectedTab: CurrentTabView = .general @AppStorage("settings.general.glass") private var glass: Bool = true @AppStorage("settings.general.dark") var isDark: Bool = true @AppStorage("settings.general.popover") private var popoverStay: Bool = true @AppStorage("settings.general.miniview") private var miniView: Bool = true @AppStorage("settings.general.selectedTheme") var selectedTheme: String = "Auto" @AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "pear-4" - private let themes = ["Auto", "Dark", "Light"] @State private var isLaunchAtLoginEnabled: Bool = false - let icons = ["externaldrive", "trash", "folder", "pear-1", "pear-1.5", "pear-2", "pear-3", "pear-4"] - @Binding var showPopover: Bool @Binding var search: String + let icons = ["externaldrive", "trash", "folder", "pear-1", "pear-1.5", "pear-2", "pear-3", "pear-4"] + private let themes = ["Auto", "Dark", "Light"] var body: some View { Form { @@ -67,7 +67,16 @@ struct InterfaceSettingsTab: View { HStack(spacing: 0) { - Image(systemName: displayMode.colorScheme == .dark ? "moon.fill" : "sun.max.fill") + Image(systemName: { + switch displayMode { + case .dark: + return "moon.fill" + case .light: + return "sun.max.fill" + case .system: + return "circle.righthalf.filled" + } + }()) .resizable() .scaledToFit() .frame(width: 20, height: 20) @@ -90,19 +99,23 @@ struct InterfaceSettingsTab: View { switch newTheme { case "Auto": displayMode.colorScheme = nil - if isDarkModeEnabled() { - displayMode.colorScheme = .dark - MenuBarExtraManager.shared.restartMenuBarExtra() - } else { - displayMode.colorScheme = .light + // Refresh foreground colors + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.selectedTab = .interface + } + if menubarEnabled{ MenuBarExtraManager.shared.restartMenuBarExtra() } case "Dark": displayMode.colorScheme = .dark - MenuBarExtraManager.shared.restartMenuBarExtra() + if menubarEnabled{ + MenuBarExtraManager.shared.restartMenuBarExtra() + } case "Light": displayMode.colorScheme = .light - MenuBarExtraManager.shared.restartMenuBarExtra() + if menubarEnabled{ + MenuBarExtraManager.shared.restartMenuBarExtra() + } default: break } @@ -148,6 +161,7 @@ struct InterfaceSettingsTab: View { if mini { appState.currentView = miniView ? .apps : .empty showPopover = false + findAndHideWindows(named: ["Pearcleaner"]) windowSettings.newWindow { MiniMode(search: $search, showPopover: $showPopover) .environmentObject(locations) @@ -164,6 +178,7 @@ struct InterfaceSettingsTab: View { } else { appState.currentView = .files } + findAndHideWindows(named: ["Pearcleaner"]) windowSettings.newWindow { RegularMode(search: $search, showPopover: $showPopover) .environmentObject(locations) diff --git a/Pearcleaner/Settings/SettingsWindow.swift b/Pearcleaner/Settings/SettingsWindow.swift index bee400b..7e8f3d5 100644 --- a/Pearcleaner/Settings/SettingsWindow.swift +++ b/Pearcleaner/Settings/SettingsWindow.swift @@ -14,10 +14,11 @@ struct SettingsView: View { @Binding var search: String @Binding var showFeature: Bool @AppStorage("settings.general.glass") private var glass: Bool = true + @AppStorage("settings.general.selectedTab") private var selectedTab: CurrentTabView = .general var body: some View { - TabView() { + TabView(selection: $selectedTab) { GeneralSettingsTab(showPopover: $showPopover, search: $search) .tabItem { Label(CurrentTabView.general.title, systemImage: "gear") @@ -47,13 +48,11 @@ struct SettingsView: View { Label(CurrentTabView.about.title, systemImage: "info.circle") } .tag(CurrentTabView.about) - } .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) - } - + } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index cd0c4e7..b57f8bb 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -17,15 +17,22 @@ struct FilesView: View { @AppStorage("settings.general.instant") var instantSearch: Bool = true @AppStorage("settings.general.brew") private var brew: Bool = false @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false + @AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true @Environment(\.colorScheme) var colorScheme @Binding var showPopover: Bool @Binding var search: String var regularWin: Bool - @State private var selectedOption = "Default" +// @State private var selectedOption = "Alpha" @State private var elapsedTime = 0 @State private var timer: Timer? = nil var body: some View { + + + let totalSelectedSize = appState.selectedItems.reduce(Int64(0)) { (result, url) in + result + (appState.appInfo.fileSize[url] ?? 0) + } + VStack(alignment: .center) { if appState.showProgress { VStack { @@ -131,7 +138,9 @@ struct FilesView: View { } } - Text("\(appState.appInfo.bundleIdentifier)").font(.title3) + Text("\(appState.appInfo.bundleIdentifier)") + .lineLimit(1) + .font(.title3) .foregroundStyle((.gray.opacity(0.8))) } .padding(.leading) @@ -140,7 +149,8 @@ struct FilesView: View { VStack(alignment: .trailing, spacing: 5) { Text("\(formatByte(size: appState.appInfo.totalSize))").font(.title).fontWeight(.bold) - 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.selectedItems.count) / \(appState.appInfo.fileSize.count) item" : "\(appState.selectedItems.count) / \(appState.appInfo.fileSize.count) items")") + .font(.callout).foregroundStyle((.gray.opacity(0.8))) } } @@ -205,9 +215,10 @@ struct FilesView: View { Spacer() Button("") { - selectedOption = selectedOption == "Default" ? "Size" : "Default" + selectedSortAlpha.toggle() +// selectedOption = selectedOption == "Alpha" ? "Size" : "Alpha" } - .buttonStyle(SimpleButtonStyle(icon: selectedOption == "Default" ? "textformat.abc" : "textformat.123", help: selectedOption == "Default" ? "Sorted alphabetically" : "Sorted by size", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size", color: Color("mode"))) } .padding() @@ -237,19 +248,20 @@ struct FilesView: View { } } - let sort = selectedOption == "Default" ? sortedFilesAlpha : sortedFilesSize + let sort = selectedSortAlpha ? sortedFilesAlpha : sortedFilesSize - ForEach(sort, id: \.self) { path in + ForEach(Array(sort.enumerated()), id: \.element) { index, path in if let fileSize = appState.appInfo.fileSize[path], let fileIcon = appState.appInfo.fileIcon[path] { let iconImage = fileIcon.map(Image.init(nsImage:)) VStack { FileDetailsItem(size: fileSize, icon: iconImage, path: path) - if path != appState.appInfo.files.last { + if index < sort.count - 1 { Divider().padding(.leading, 40).opacity(0.5) } } } } + } .padding() } @@ -261,97 +273,64 @@ struct FilesView: View { Spacer() - if !appState.selectedItems.isEmpty { - Button("Uninstall") { - Task { - updateOnMain { - search = "" - if !regularWin { - appState.currentView = .apps + Button("\(formatByte(size: totalSelectedSize))") { + Task { + updateOnMain { + search = "" + if !regularWin { + appState.currentView = .apps + showPopover = false + } else { + appState.currentView = .empty + } + } + let selectedItemsArray = Array(appState.selectedItems) + + killApp(appId: appState.appInfo.bundleIdentifier) { + moveFilesToTrash(at: selectedItemsArray) { + withAnimation { showPopover = false + updateOnMain { + appState.currentView = mini ? .apps : .empty + appState.isReminderVisible.toggle() + } + if sentinel { + launchctl(load: true) + } + } + + // Brew cleanup if enabled + if brew { + caskCleanup(app: appState.appInfo.appName) + } + + // Remove app from app list if main app bundle is removed + if selectedItemsArray.contains(where: { $0.absoluteString == appState.appInfo.path.absoluteString }) { + removeApp(appState: appState, withId: appState.appInfo.id) } else { - appState.currentView = .empty + // 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 } - let selectedItemsArray = Array(appState.selectedItems) - - // Delete all folders above app bundle between /Applications and app bundle file - -// if let url = URL(string: appState.appInfo.path.absoluteString) { -// var parentURL = url.deletingLastPathComponent() // Immediate parent of the .app bundle -// -// // Traverse up the path components until just below /Applications or ~/Applications -// while parentURL.pathComponents.count > 2 && parentURL.path.contains("Applications") && parentURL.deletingLastPathComponent().path != "/Applications" && parentURL.deletingLastPathComponent().path != "\(home)/Applications" { -// parentURL = parentURL.deletingLastPathComponent() -// } -// -// // Now, parentURL should be the directory just below /Applications or ~/Applications -// if parentURL.path != "/Applications" && parentURL.path != "\(home)/Applications" { -// selectedItemsArray.insert(parentURL, at: 0) // Add the correct parent directory to the array -// } -// } - - // Delete folder only 1 up from app bundle between /Applications and app bundle file - -// if let url = URL(string: appState.appInfo.path.absoluteString) { -// let appFolderURL = appState.appInfo.path.absoluteString.contains("Wrapper") ? url.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() : url.deletingLastPathComponent() // Get the immediate parent directory of regular and wrapped apps -// -// if appFolderURL.path == "/Applications" || appFolderURL.path == "\(home)/Applications" { -// // Do nothing, skip insertion -// } else if appFolderURL.pathComponents.count > 2 && appFolderURL.path.contains("Applications") { -// // Insert into selectedItemsArray only if there is an intermediary folder -// selectedItemsArray.insert(appFolderURL, at: 0) -// } -// } - - killApp(appId: appState.appInfo.bundleIdentifier) { - moveFilesToTrash(at: selectedItemsArray) { - withAnimation { - showPopover = false - updateOnMain { - appState.currentView = mini ? .apps : .empty - appState.isReminderVisible.toggle() - } - if sentinel { - launchctl(load: true) - } - } - - // Brew cleanup if enabled - if brew { - caskCleanup(app: appState.appInfo.appName) - } - - // Remove app from app list if main app bundle is removed - if selectedItemsArray.contains(where: { $0.absoluteString == appState.appInfo.path.absoluteString }) { - 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 - } - } - } } - .buttonStyle(NavButtonBottomBarStyle(image: "trash.fill", help: "Uninstall")) - } else { - Text("No files selected to remove").font(.title).foregroundStyle(Color("mode")).opacity(0.2) - .padding(5) } + .buttonStyle(UninstallButton(isEnabled: !appState.selectedItems.isEmpty)) + .disabled(appState.selectedItems.isEmpty) + .padding(.top) - Spacer() +// Spacer() } diff --git a/Pearcleaner/Views/RegularMode.swift b/Pearcleaner/Views/RegularMode.swift index 475f5c1..25cac1a 100644 --- a/Pearcleaner/Views/RegularMode.swift +++ b/Pearcleaner/Views/RegularMode.swift @@ -29,6 +29,7 @@ struct RegularMode: View { } } +// let slate = Color(.sRGB, red: 0.188143, green: 0.208556, blue: 0.262679, opacity: 1) var body: some View { @@ -103,6 +104,7 @@ struct RegularMode: View { } .transition(.opacity) } +// .background(slate) // .onHover { hovering in // withAnimation(Animation.easeInOut(duration: 0.4)) { // isHovering = hovering diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index 2f19f5f..509e245 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -16,24 +16,29 @@ struct ZombieView: View { @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false @AppStorage("settings.general.instant") private var instantSearch: Bool = true @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false + @AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true @Environment(\.colorScheme) var colorScheme @Binding var showPopover: Bool @Binding var search: String @State private var searchZ: String = "" - @State private var selectedOption = "Default" +// @State private var selectedOption = "Alpha" var regularWin: Bool @State private var elapsedTime = 0 @State private var timer: Timer? = nil var body: some View { + let totalSelectedZombieSize = appState.selectedZombieItems.reduce(Int64(0)) { (result, url) in + result + (appState.zombieFile.fileSize[url] ?? 0) + } + let filteredAndSortedFiles: ([URL], Int64) = { let filteredFiles = appState.zombieFile.fileSize.filter { (url, _) in searchZ.isEmpty || url.lastPathComponent.localizedCaseInsensitiveContains(searchZ) } let sortedFilteredFiles = filteredFiles.sorted(by: { - if selectedOption == "Default" { + if selectedSortAlpha { return $0.key.lastPathComponent.pearFormat() < $1.key.lastPathComponent.pearFormat() } else { return $0.value > $1.value @@ -148,7 +153,9 @@ struct ZombieView: View { VStack(alignment: .trailing, spacing: 5) { Text("\(formatByte(size: filteredAndSortedFiles.1))").font(.title).fontWeight(.bold) - Text("\(filteredAndSortedFiles.0.count == 1 ? "\(filteredAndSortedFiles.0.count) item" : "\(filteredAndSortedFiles.0.count) items")").font(.callout).underline().foregroundStyle((.gray.opacity(0.8))) + + Text("\(appState.zombieFile.fileSize.count == 1 ? "\(appState.selectedZombieItems.count) / \(appState.zombieFile.fileSize.count) item" : "\(appState.selectedZombieItems.count) / \(appState.zombieFile.fileSize.count) items")") + .font(.callout).foregroundStyle((.gray.opacity(0.8))) } } @@ -158,10 +165,6 @@ struct ZombieView: View { .padding(.top, 0) } -// SearchBarMiniBottom(search: $searchZ) -// .padding(.top) -// .padding(.horizontal) - // Item selection and sorting toolbar HStack { Toggle("", isOn: Binding( @@ -177,12 +180,11 @@ struct ZombieView: View { // .padding(.top) .padding(.horizontal) -// Spacer() Button("") { - selectedOption = selectedOption == "Default" ? "Size" : "Default" + selectedSortAlpha.toggle() } - .buttonStyle(SimpleButtonStyle(icon: selectedOption == "Default" ? "textformat.abc" : "textformat.123", help: selectedOption == "Default" ? "Sorted alphabetically" : "Sorted by size", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size", color: Color("mode"))) } @@ -193,22 +195,22 @@ struct ZombieView: View { Divider() .padding(.horizontal) - - ScrollView() { LazyVStack { - ForEach(filteredAndSortedFiles.0, id: \.self) { file in + ForEach(Array(filteredAndSortedFiles.0.enumerated()), id: \.element) { index, file in if let fileSize = appState.zombieFile.fileSize[file], let fileIcon = appState.zombieFile.fileIcon[file] { let iconImage = fileIcon.map(Image.init(nsImage:)) + VStack { + ZombieFileDetailsItem(size: fileSize, icon: iconImage, path: file) + .padding(.trailing) - ZombieFileDetailsItem(size: fileSize, icon: iconImage, path: file) - .padding(.trailing) - - if file != appState.zombieFile.fileSize.keys.sorted(by: { $0.absoluteString < $1.absoluteString }).last { - Divider().padding(.leading, 40).opacity(0.5) + if index < filteredAndSortedFiles.0.count - 1 { + Divider().padding(.leading, 40).opacity(0.5) + } } } } + } .padding() } @@ -217,26 +219,9 @@ struct ZombieView: View { HStack() { -// Picker("", selection: Binding( -// get: { appState.selectedZombieItems.count == appState.zombieFile.fileSize.count ? true : false }, -// set: { newValue in -// updateOnMain { -// appState.selectedZombieItems = newValue ? Set(appState.zombieFile.fileSize.keys) : [] -// } -// } -// )) { -// Image(systemName: "checkmark.square").tag(true) -// Image(systemName: "square").tag(false) -// } -// .pickerStyle(SegmentedPickerStyle()) -// .frame(width: 100) -// .offset(x: -8) -// .help("Item Selection") - Spacer() - if !appState.selectedZombieItems.isEmpty { - Button("Remove") { + Button("\(formatByte(size: totalSelectedZombieSize))") { Task { updateOnMain { appState.zombieFile = .empty @@ -265,21 +250,13 @@ struct ZombieView: View { } } - .buttonStyle(NavButtonBottomBarStyle(image: "trash.fill", help: "Remove")) - } else { - Text("No files selected to remove").font(.title).foregroundStyle(Color("mode")).opacity(0.2) - .padding(5) - } + .buttonStyle(UninstallButton(isEnabled: !appState.selectedZombieItems.isEmpty)) + .disabled(appState.selectedZombieItems.isEmpty) + .padding(.top) - 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") +// Spacer() + } }