From faa7ec9457a823bba6ef28facbff54bc2c54e8de Mon Sep 17 00:00:00 2001 From: Alin Date: Tue, 27 Feb 2024 17:08:38 -0700 Subject: [PATCH] More --- Pearcleaner/Logic/Logic.swift | 11 +- Pearcleaner/Logic/Styles.swift | 59 +++++++++ Pearcleaner/PearcleanerApp.swift | 2 +- Pearcleaner/Settings/General.swift | 7 +- Pearcleaner/Settings/SettingsWindow.swift | 5 +- Pearcleaner/Views/FilesView.swift | 145 +++++++++------------- Pearcleaner/Views/MiniMode.swift | 2 +- Pearcleaner/Views/TopBar.swift | 2 +- Pearcleaner/Views/TopBarMini.swift | 1 + Pearcleaner/Views/ZombieView.swift | 83 +++++++------ 10 files changed, 177 insertions(+), 140 deletions(-) diff --git a/Pearcleaner/Logic/Logic.swift b/Pearcleaner/Logic/Logic.swift index 8ce9617..66b9614 100644 --- a/Pearcleaner/Logic/Logic.swift +++ b/Pearcleaner/Logic/Logic.swift @@ -600,7 +600,7 @@ func reversePathsSearch(appState: AppState, locations: Locations, completion: @e } - let sortedCollection = collection.sorted(by: { $0.absoluteString < $1.absoluteString }) +// let sortedCollection = collection.sorted(by: { $0.absoluteString < $1.absoluteString }) // Calculate file details (sizes and icons) var fileSize: [URL: Int64] = [:] @@ -610,10 +610,8 @@ func reversePathsSearch(appState: AppState, locations: Locations, completion: @e for path in collection { var size: Int64 var icon: NSImage? = nil -// size = 0 size = totalSizeOnDisk(for: path) icon = getIconForFileOrFolderNS(atPath: path) -// icon = nil fileSize[path] = size fileIcon[path] = icon } @@ -622,15 +620,10 @@ func reversePathsSearch(appState: AppState, locations: Locations, completion: @e dispatchGroup.notify(queue: .main) { updateOnMain { -// updatedZombieFile.files = sortedCollection updatedZombieFile.fileSize = fileSize updatedZombieFile.fileIcon = fileIcon - appState.selectedZombieItems = Set(sortedCollection) +// appState.selectedZombieItems = Set(sortedCollection) appState.zombieFile = updatedZombieFile - -// print(updatedZombieFile.fileSize.keys.count) -// print(updatedZombieFile) - appState.showProgress = false } diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index d93223a..03dd049 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -50,6 +50,65 @@ struct SimpleButtonStyle: ButtonStyle { } +struct NavButtonBottomBarStyle: ButtonStyle { + @State private var isHovered = false + var image: String + var help: String + + func makeBody(configuration: Configuration) -> some View { + ZStack { + Image(systemName: image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 20, height: 20) + .foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5)) + Rectangle() + .foregroundColor(.clear) + .frame(width: 36, height: 36) + .cornerRadius(6) + .contentShape(Rectangle()) + } + .scaleEffect(configuration.isPressed ? 0.95 : 1) + .buttonStyle(.plain) + .onHover { inside in + isHovered = inside + } + .help(help) + } +} + + +struct WarningPopoverView: View { + var label: String + var bodyText: String + + @Binding var isPresented: Bool + + var body: some View { + HStack { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundStyle(.red) + .popover(isPresented: $isPresented, arrowEdge: .top) { + VStack { + Text(bodyText) + .padding() + .font(.title2) + } + } + + Text(label) + .foregroundStyle(Color.red) + + Spacer() + } + .onTapGesture { + isPresented.toggle() + } + } +} + + + struct LabeledDivider: View { let label: String @EnvironmentObject var appState: AppState diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index f293c80..10cfa41 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -133,7 +133,7 @@ struct PearcleanerApp: App { Settings { - SettingsView() + SettingsView(showPopover: $showPopover) .environmentObject(appState) .toolbarBackground(.clear) .preferredColorScheme(displayMode.colorScheme) diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index c9a6d6f..c2c3808 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -20,7 +20,8 @@ struct GeneralSettingsTab: View { @AppStorage("displayMode") var displayMode: DisplayMode = .system @State private var selectedTheme = "Auto" private let themes = ["Auto", "Dark", "Light"] - + @Binding var showPopover: Bool + var body: some View { Form { VStack { @@ -117,9 +118,9 @@ struct GeneralSettingsTab: View { .toggleStyle(.switch) .onChange(of: mini) { newVal in if mini { + appState.currentView = miniView ? .apps : .empty + showPopover = false resizeWindowAuto(windowSettings: windowSettings) -// showPopover = false -// appState.currentView = miniView ? .apps : .empty } else { resizeWindowAuto(windowSettings: windowSettings) if appState.appInfo.appName.isEmpty { diff --git a/Pearcleaner/Settings/SettingsWindow.swift b/Pearcleaner/Settings/SettingsWindow.swift index 626a021..3bdec53 100644 --- a/Pearcleaner/Settings/SettingsWindow.swift +++ b/Pearcleaner/Settings/SettingsWindow.swift @@ -9,11 +9,12 @@ import SwiftUI struct SettingsView: View { @EnvironmentObject var appState: AppState - + @Binding var showPopover: Bool + var body: some View { TabView() { - GeneralSettingsTab() + GeneralSettingsTab(showPopover: $showPopover) .tabItem { Label(CurrentTabView.general.title, systemImage: "gear") } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index dab7559..c8e393b 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -18,7 +18,6 @@ struct FilesView: View { @Binding var showPopover: Bool @Binding var search: String @State private var selectedOption = "Default" - @State private var toggles: Bool = true var body: some View { VStack(alignment: .center) { @@ -88,49 +87,35 @@ struct FilesView: View { // .foregroundStyle(Color("AccentColor").opacity(0.7)) .underline() } - if appState.appInfo.webApp { - HStack { - Text("web") - .font(.footnote) - .foregroundStyle(Color("mode").opacity(0.5)) - .frame(minWidth: 30, minHeight: 15) - .padding(2) - .background(Color("mode").opacity(0.1)) - .clipShape(.capsule) - Spacer() - } - - } - if appState.appInfo.appName.count < 5 { - HStack(alignment: .center) { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundStyle(.red) - .popover(isPresented: $showPop, arrowEdge: .top) { - VStack() { - Text("Pearcleaner searches for files via a combination of bundle id and app name.\n**\(appState.appInfo.appName)** has a common or short app name so there might be unrelated files found.\nPlease check the list thoroughly before uninstalling.") - .padding() - .font(.title2) - } - - } - - Text("Warning") - .foregroundStyle(Color.red) - Spacer() + HStack() { + if appState.appInfo.webApp { + HStack { + Text("web") + .font(.footnote) + .foregroundStyle(Color("mode").opacity(0.5)) + .frame(minWidth: 30, minHeight: 15) + .padding(2) + .background(Color("mode").opacity(0.1)) + .clipShape(.capsule) + Spacer() + } + } - .padding(.top) - - .onTapGesture { - showPop = true + if appState.appInfo.appName.count < 5 { + WarningPopoverView(label: "Caution", + bodyText: "Pearcleaner searches for files via a combination of bundle id and app name.\n\(appState.appInfo.appName) has a common or short app name so there might be unrelated files found.\nPlease check the list thoroughly before uninstalling.", + isPresented: $showPop) } } + .padding(.vertical, 5) + } .padding(20) } -// .padding(.horizontal) +// .padding(.bottom) .background( RoundedRectangle(cornerRadius: 8) // .strokeBorder(Color("AccentColor"), lineWidth: 0.5) @@ -140,34 +125,15 @@ struct FilesView: View { // .strokeBorder(Color("AccentColor").opacity(colorScheme == .dark ? 0.1 : 0.1), lineWidth: 1) // ) ) - - - HStack(alignment: .center) { - Spacer() - - Text("\(toggles ? "Selected: All" : "Selected: None")").font(.subheadline) - Toggle("", isOn: $toggles) - .onChange(of: toggles) { value in - if value { - updateOnMain { - appState.selectedItems = Set(appState.appInfo.files) - } - } else { - updateOnMain { - appState.selectedItems.removeAll() - } - } - } - } - .padding(.top) + .padding(.bottom) ScrollView() { VStack { let sortedFilesSize = appState.appInfo.files.sorted(by: { appState.appInfo.fileSize[$0, default: 0] > appState.appInfo.fileSize[$1, default: 0] }) -// let sortedFilesAlpha = appState.appInfo.files.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) + let sortedFilesAlpha = appState.appInfo.files - let sort = selectedOption == "Default" ? appState.appInfo.files : sortedFilesSize + let sort = selectedOption == "Default" ? sortedFilesAlpha : sortedFilesSize ForEach(sort, id: \.self) { path in if let fileSize = appState.appInfo.fileSize[path], let fileIcon = appState.appInfo.fileIcon[path] { @@ -195,20 +161,35 @@ struct FilesView: View { } } } + .padding() } - .padding([.bottom]) - +// .background( +// RoundedRectangle(cornerRadius: 8) +// .fill(Color("mode").opacity(colorScheme == .dark ? 0.05 : 0.05)) +// ) + HStack() { - Picker(selection: $selectedOption, label: Text("Sort")) { - Text("Default").tag("Default") - Text("Size").tag("Size") + + Picker("", selection: Binding( + get: { appState.selectedItems.count == appState.appInfo.files.count ? true : false }, + set: { newValue in + updateOnMain { + appState.selectedItems = newValue ? Set(appState.appInfo.files) : [] + } + } + )) { + Text("􀃲").tag(true) + Text("􀂒").tag(false) } .pickerStyle(SegmentedPickerStyle()) - .frame(width: 150) + .frame(width: 100) + .offset(x: -8) + .help("Item Selection") Spacer() if mini { + Button("Close") { updateOnMain { appState.appInfo = AppInfo.empty @@ -217,7 +198,8 @@ struct FilesView: View { showPopover = false } } -// .buttonStyle(FilesViewActionButton(action: .close)) + .buttonStyle(NavButtonBottomBarStyle(image: "x.circle.fill", help: "Close")) + } @@ -274,15 +256,25 @@ struct FilesView: View { } } + .buttonStyle(NavButtonBottomBarStyle(image: "trash.fill", help: "Uninstall")) .disabled(appState.selectedItems.isEmpty) -// .buttonStyle(FilesViewActionButton(action: .uninstall)) + + Spacer() + + Picker("", selection: $selectedOption) { + Text("􀅐").tag("Default") + Text("􀆃").tag("Size") + } + .pickerStyle(SegmentedPickerStyle()) + .frame(width: 100) + .help("Sorting Selection") } -// .padding(.top) + .padding(.top) } .transition(.opacity) .padding(20) - + } } @@ -323,25 +315,8 @@ struct FileDetailsItem: View { self.appState.selectedItems.remove(self.path) } } -// get: { self.appState.selectedItems.contains(self.path) }, -// set: { isChecked in -// if isChecked { -// self.appState.selectedItems.insert(self.path) -// if self.path == appState.appInfo.path { -// self.appState.appInfo.fileSize.keys.forEach { -// self.appState.selectedItems.insert($0) -// } -// } -// } else { -// self.appState.selectedItems.remove(self.path) -// if self.path == appState.appInfo.path { -// self.appState.selectedItems.forEach { -// self.appState.selectedItems.remove($0) -// } -// } -// } -// } )) + .disabled(self.path.path.contains(".Trash")) if let appIcon = icon { diff --git a/Pearcleaner/Views/MiniMode.swift b/Pearcleaner/Views/MiniMode.swift index 0bfa097..ffa2161 100644 --- a/Pearcleaner/Views/MiniMode.swift +++ b/Pearcleaner/Views/MiniMode.swift @@ -59,7 +59,7 @@ struct MiniMode: View { .fill(Color("pop")) .padding(-80) ) - .frame(width: 650, height: 500) + .frame(width: 650, height: 550) } diff --git a/Pearcleaner/Views/TopBar.swift b/Pearcleaner/Views/TopBar.swift index a9be0ed..e92d737 100644 --- a/Pearcleaner/Views/TopBar.swift +++ b/Pearcleaner/Views/TopBar.swift @@ -46,7 +46,7 @@ struct TopBar: View { appState.appInfo = AppInfo.empty } } - .buttonStyle(SimpleButtonStyle(icon: "house", help: "Home", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", help: "Drop Target", color: Color("mode"))) } diff --git a/Pearcleaner/Views/TopBarMini.swift b/Pearcleaner/Views/TopBarMini.swift index d372e61..67fb7a6 100644 --- a/Pearcleaner/Views/TopBarMini.swift +++ b/Pearcleaner/Views/TopBarMini.swift @@ -88,6 +88,7 @@ struct TopBarMini: View { Button("") { withAnimation(.easeInOut(duration: 0.5)) { + showPopover = false updateOnMain { if appState.zombieFile.fileSize.keys.count == 0 { appState.currentView = .zombie diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index 71a96d9..0f31150 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -17,7 +17,7 @@ struct ZombieView: View { @Environment(\.colorScheme) var colorScheme @Binding var showPopover: Bool @Binding var search: String - @State private var toggles: Bool = true + @State private var selectedOption = "Default" var body: some View { VStack(alignment: .center) { @@ -87,43 +87,11 @@ struct ZombieView: View { // .foregroundStyle(Color("AccentColor").opacity(0.7)) .underline() } - HStack() { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundStyle(.red) - .popover(isPresented: $showPop, arrowEdge: .top) { - VStack() { - Text("Leftover file search is not 100% accurate as it doesn't have any app bundles to check against.\nThis searches for files/folders and excludes the ones that have overlap with your currently installed apps. \nMake sure to confirm files marked for removal are correct.") - .padding() - .font(.title2) - } - } - - Text("Warning") - .foregroundStyle(Color.red) - - Spacer() - Toggle("\(toggles ? "Selected: All" : "Selected: None")", isOn: $toggles) - .controlSize(.small) -// .toggleStyle(.switch) - .onChange(of: toggles) { value in - if value { - updateOnMain { - appState.selectedZombieItems = Set(appState.zombieFile.fileSize.keys) - } - } else { - updateOnMain { - appState.selectedZombieItems.removeAll() - } - } - } - } - .padding(.top) - .onTapGesture { - showPop = true - } - - + WarningPopoverView(label: "Caution - Read", + bodyText: "Leftover file search is not 100% accurate as it doesn't have any app bundles to check against.\nThis searches for files/folders and excludes the ones that have overlap with your currently installed apps. \nMake sure to confirm files marked for removal are correct.", + isPresented: $showPop) + .padding(.vertical, 5) } @@ -144,7 +112,14 @@ struct ZombieView: View { ScrollView() { LazyVStack { - ForEach(appState.zombieFile.fileSize.keys.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }), id: \.self) { file in + let sortedFilesSize = appState.zombieFile.fileSize.keys.sorted(by: { appState.zombieFile.fileSize[$0, default: 0] > appState.zombieFile.fileSize[$1, default: 0] }) + + + let sortedFilesAlpha = appState.zombieFile.fileSize.keys.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) + + let sort = selectedOption == "Default" ? sortedFilesAlpha : sortedFilesSize + + ForEach(sort, id: \.self) { file in if let fileSize = appState.zombieFile.fileSize[file], let fileIcon = appState.zombieFile.fileIcon[file] { let iconImage = fileIcon.map(Image.init(nsImage:)) @@ -162,6 +137,23 @@ struct ZombieView: View { .padding() 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) : [] + } + } + )) { + Text("􀃲").tag(true) + Text("􀂒").tag(false) + } + .pickerStyle(SegmentedPickerStyle()) + .frame(width: 100) + .offset(x: -8) + .help("Item Selection") + Spacer() if mini { @@ -173,6 +165,8 @@ struct ZombieView: View { showPopover = false } } + .buttonStyle(NavButtonBottomBarStyle(image: "x.circle.fill", help: "Close")) + } @@ -183,6 +177,8 @@ struct ZombieView: View { reversePathsSearch(appState: appState, locations: locations) } } + .buttonStyle(NavButtonBottomBarStyle(image: "arrow.counterclockwise.circle.fill", help: "Rescan files")) + Button("Remove") { Task { @@ -217,7 +213,18 @@ struct ZombieView: View { } } + .buttonStyle(NavButtonBottomBarStyle(image: "trash.fill", help: "Remove")) .disabled(appState.selectedZombieItems.isEmpty) + + Spacer() + + Picker("", selection: $selectedOption) { + Text("􀅐").tag("Default") + Text("􀆃").tag("Size") + } + .pickerStyle(SegmentedPickerStyle()) + .frame(width: 100) + .help("Sorting Selection") } }