From a54371fb144ee5f77f1aa40d810e4d46f1a4a3c6 Mon Sep 17 00:00:00 2001 From: Alin Date: Sat, 27 Apr 2024 19:28:33 -0600 Subject: [PATCH] v3.6.0 --- Pearcleaner.xcodeproj/project.pbxproj | 24 +- Pearcleaner/Logic/AppCommands.swift | 2 +- Pearcleaner/Logic/AppPathsFetch.swift | 9 +- Pearcleaner/Logic/AppState.swift | 3 +- Pearcleaner/Logic/ReversePathsFetch.swift | 1 + Pearcleaner/Logic/Styles.swift | 141 ++++++------ Pearcleaner/Logic/Utilities.swift | 3 + Pearcleaner/PearcleanerApp.swift | 2 +- .../button.colorset/Contents.json | 12 +- Pearcleaner/Settings/Interface.swift | 2 +- Pearcleaner/Views/AppListItems.swift | 5 +- Pearcleaner/Views/AppsListView.swift | 69 +++++- Pearcleaner/Views/FilesView.swift | 110 +++++---- Pearcleaner/Views/MenuBarMiniAppView.swift | 142 ------------ Pearcleaner/Views/MiniMode.swift | 148 +++++------- Pearcleaner/Views/RegularMode.swift | 115 ++-------- Pearcleaner/Views/Searchbar.swift | 213 ++++++++++++++++++ Pearcleaner/Views/TopBar.swift | 96 -------- Pearcleaner/Views/TopBarMini.swift | 128 ----------- Pearcleaner/Views/ZombieView.swift | 135 ++++++----- 20 files changed, 591 insertions(+), 769 deletions(-) delete mode 100644 Pearcleaner/Views/MenuBarMiniAppView.swift create mode 100644 Pearcleaner/Views/Searchbar.swift delete mode 100644 Pearcleaner/Views/TopBar.swift delete mode 100644 Pearcleaner/Views/TopBarMini.swift diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index 937152b..81c0007 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ C7045A282B03E71D00376976 /* MiniMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A272B03E71D00376976 /* MiniMode.swift */; }; - C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A292B03FAF000376976 /* TopBarMini.swift */; }; C7045A322B068AD700376976 /* NewWin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A312B068AD700376976 /* NewWin.swift */; }; C7045A342B068B2900376976 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A332B068B2900376976 /* UpdateView.swift */; }; C7045A362B068DD700376976 /* PermView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A352B068DD700376976 /* PermView.swift */; }; @@ -39,7 +38,6 @@ C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A9CE462B89164700EB6E78 /* Authorization.swift */; }; C7ACADE62B92A737000B5845 /* Features.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ACADE52B92A737000B5845 /* Features.swift */; }; C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CF47232B3B3F1700979C5F /* WindowSettings.swift */; }; - C7D31D482AFEB23700C7ED9E /* TopBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D472AFEB23700C7ED9E /* TopBar.swift */; }; C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.swift */; }; C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D502AFF00F300C7ED9E /* Locations.swift */; }; C7DC1FEE2B9F7D3D009AC317 /* features.json in Resources */ = {isa = PBXBuildFile; fileRef = C7DC1FED2B9F7D3D009AC317 /* features.json */; }; @@ -47,11 +45,11 @@ C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49ED2BAB7F6000CCBA16 /* ReversePathsFetch.swift */; }; C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49EF2BABA14400CCBA16 /* Folders.swift */; }; C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */; }; - C7DE672D2BA6356500EB1633 /* MenuBarMiniAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */; }; C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B02BA8A35500F13DE4 /* Interface.swift */; }; C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B22BA8ED8800F13DE4 /* Trash.swift */; }; C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; }; C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7FB173A2B96321300B96F9A /* AppsListView.swift */; }; + C7FEBA112BDC422200AE195F /* Searchbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7FEBA102BDC422200AE195F /* Searchbar.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -99,7 +97,6 @@ /* Begin PBXFileReference section */ C7045A272B03E71D00376976 /* MiniMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniMode.swift; sourceTree = ""; }; - C7045A292B03FAF000376976 /* TopBarMini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBarMini.swift; sourceTree = ""; }; C7045A312B068AD700376976 /* NewWin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewWin.swift; sourceTree = ""; }; C7045A332B068B2900376976 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = ""; }; C7045A352B068DD700376976 /* PermView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermView.swift; sourceTree = ""; }; @@ -135,7 +132,6 @@ C7A9CE462B89164700EB6E78 /* Authorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = ""; }; C7ACADE52B92A737000B5845 /* Features.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features.swift; sourceTree = ""; }; C7CF47232B3B3F1700979C5F /* WindowSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowSettings.swift; sourceTree = ""; }; - C7D31D472AFEB23700C7ED9E /* TopBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBar.swift; sourceTree = ""; }; C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = ""; }; C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = ""; }; C7DC1FED2B9F7D3D009AC317 /* features.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = features.json; sourceTree = SOURCE_ROOT; }; @@ -143,11 +139,11 @@ C7DD49ED2BAB7F6000CCBA16 /* ReversePathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReversePathsFetch.swift; sourceTree = ""; }; C7DD49EF2BABA14400CCBA16 /* Folders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Folders.swift; sourceTree = ""; }; C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarItem.swift; sourceTree = ""; }; - C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarMiniAppView.swift; sourceTree = ""; }; C7ED86B02BA8A35500F13DE4 /* Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interface.swift; sourceTree = ""; }; C7ED86B22BA8ED8800F13DE4 /* Trash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trash.swift; sourceTree = ""; }; C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; C7FB173A2B96321300B96F9A /* AppsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppsListView.swift; sourceTree = ""; }; + C7FEBA102BDC422200AE195F /* Searchbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Searchbar.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -300,12 +296,10 @@ C77B90222AF2D616009CC655 /* FilesView.swift */, C71848432B8D2D600046CB13 /* ZombieView.swift */, C76D08542AF89CDE00D07867 /* RegularMode.swift */, - C7D31D472AFEB23700C7ED9E /* TopBar.swift */, - C7045A292B03FAF000376976 /* TopBarMini.swift */, C7D31D492AFEB26700C7ED9E /* AppListItems.swift */, C7045A272B03E71D00376976 /* MiniMode.swift */, C7FB173A2B96321300B96F9A /* AppsListView.swift */, - C7DE672C2BA6356500EB1633 /* MenuBarMiniAppView.swift */, + C7FEBA102BDC422200AE195F /* Searchbar.swift */, ); path = Views; sourceTree = ""; @@ -467,11 +461,9 @@ C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */, C7045A322B068AD700376976 /* NewWin.swift in Sources */, C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */, - C7DE672D2BA6356500EB1633 /* MenuBarMiniAppView.swift in Sources */, C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */, C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */, C77B901A2AF1938F009CC655 /* Logic.swift in Sources */, - C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */, C77B901C2AF193A3009CC655 /* Styles.swift in Sources */, C7045A282B03E71D00376976 /* MiniMode.swift in Sources */, C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */, @@ -488,12 +480,12 @@ C771CB762BD028AF00C33A18 /* SegmentedPicker.swift in Sources */, C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */, C79D05202BC99EB40083F976 /* ThemeManager.swift in Sources */, - C7D31D482AFEB23700C7ED9E /* TopBar.swift in Sources */, C74FDC632BCD833D00B8960F /* Conditions.swift in Sources */, C7ACADE62B92A737000B5845 /* Features.swift in Sources */, C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */, C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */, C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */, + C7FEBA112BDC422200AE195F /* Searchbar.swift in Sources */, C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */, C77B90232AF2D616009CC655 /* FilesView.swift in Sources */, ); @@ -564,8 +556,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_BUILD = 38; - APP_VERSION = 3.5.4; + APP_BUILD = 39; + APP_VERSION = 3.6.0; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -634,8 +626,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_BUILD = 38; - APP_VERSION = 3.5.4; + APP_BUILD = 39; + APP_VERSION = 3.6.0; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/Pearcleaner/Logic/AppCommands.swift b/Pearcleaner/Logic/AppCommands.swift index e2e89db..8762282 100644 --- a/Pearcleaner/Logic/AppCommands.swift +++ b/Pearcleaner/Logic/AppCommands.swift @@ -69,7 +69,7 @@ struct AppCommands: Commands { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { appState.sortedApps = sortedApps for app in appState.trashedFiles { - AppPathFinder(appInfo: app, appState: appState, locations: locations).findPaths() + AppPathFinder(appInfo: app, appState: appState, locations: locations, undo: true).findPaths() } } } diff --git a/Pearcleaner/Logic/AppPathsFetch.swift b/Pearcleaner/Logic/AppPathsFetch.swift index d5eaab7..446595e 100644 --- a/Pearcleaner/Logic/AppPathsFetch.swift +++ b/Pearcleaner/Logic/AppPathsFetch.swift @@ -16,16 +16,18 @@ class AppPathFinder { private var locations: Locations private var backgroundRun: Bool private var reverseAddon: Bool + private var undo: Bool private var completion: () -> Void = {} private var collection: [URL] = [] private let collectionAccessQueue = DispatchQueue(label: "com.alienator88.Pearcleaner.appPathFinder.collectionAccess") - init(appInfo: AppInfo = .empty, appState: AppState, locations: Locations, backgroundRun: Bool = false, reverseAddon: Bool = false, completion: @escaping () -> Void = {}) { + init(appInfo: AppInfo = .empty, appState: AppState, locations: Locations, backgroundRun: Bool = false, reverseAddon: Bool = false, undo: Bool = false, completion: @escaping () -> Void = {}) { self.appInfo = appInfo self.appState = appState self.locations = locations self.backgroundRun = backgroundRun self.reverseAddon = reverseAddon + self.undo = undo self.completion = completion } @@ -306,7 +308,10 @@ class AppPathFinder { if !self.backgroundRun { self.appState.appInfo = self.appInfo - self.appState.selectedItems = Set(updatedCollection) + if !self.undo { + self.appState.selectedItems = Set(updatedCollection) + + } } // Append object to store if running reverse search with empty store diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index db836b3..3e2835c 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -24,14 +24,13 @@ class AppState: ObservableObject @Published var currentView = CurrentDetailsView.empty @Published var showAlert: Bool = false @Published var sidebar: Bool = true -// @Published var isReminderVisible: Bool = false @Published var releases = [Release]() @Published var progressBar: (String, Double) = ("Ready", 0.0) @Published var reload: Bool = false @Published var showProgress: Bool = false @Published var finderExtensionEnabled: Bool = false - + init() { self.appInfo = AppInfo( id: UUID(), diff --git a/Pearcleaner/Logic/ReversePathsFetch.swift b/Pearcleaner/Logic/ReversePathsFetch.swift index 83b781c..53e640d 100644 --- a/Pearcleaner/Logic/ReversePathsFetch.swift +++ b/Pearcleaner/Logic/ReversePathsFetch.swift @@ -39,6 +39,7 @@ class ReversePathsSearcher { let allNames = appState.appInfoStore.map { $0.appName.pearFormat() } for location in locations.reverse.paths where fileManager.fileExists(atPath: location) { + dispatchGroup.enter() processLocation(location, allPaths: allPaths, allNames: allNames) dispatchGroup.leave() diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index ac5ec81..c1e3b4a 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -11,22 +11,26 @@ import SwiftUI struct SimpleButtonStyle: ButtonStyle { @State private var hovered = false let icon: String + let iconFlip: String let label: String let help: String let color: Color let size: CGFloat + let padding: CGFloat - init(icon: String, label: String = "", help: String, color: Color = Color("mode"), size: CGFloat = 20) { + init(icon: String, iconFlip: String = "", label: String = "", help: String, color: Color = Color("mode"), size: CGFloat = 20, padding: CGFloat = 5) { self.icon = icon + self.iconFlip = iconFlip self.label = label self.help = help self.color = color self.size = size + self.padding = padding } func makeBody(configuration: Self.Configuration) -> some View { - HStack { - Image(systemName: icon) + HStack(alignment: .center) { + Image(systemName: (hovered && !iconFlip.isEmpty) ? iconFlip : icon) .resizable() .scaledToFit() .frame(width: size, height: size) @@ -35,7 +39,7 @@ struct SimpleButtonStyle: ButtonStyle { } } .foregroundColor(hovered ? color : color.opacity(0.5)) - .padding(5) + .padding(padding) .onHover { hovering in withAnimation() { hovered = hovering @@ -121,6 +125,35 @@ struct ResetSettingsButtonStyle: ButtonStyle { } +struct SimpleCheckboxToggleStyle: ToggleStyle { + @EnvironmentObject private var themeSettings: ThemeSettings + @State private var isHovered: Bool = false + + func makeBody(configuration: Configuration) -> some View { + HStack { + RoundedRectangle(cornerRadius: 4) + .fill(themeSettings.themeColor.darker(by: 5)) + .frame(width: 18, height: 18) + .overlay { + if configuration.isOn { + Image(systemName: "checkmark") + .foregroundStyle(Color("mode")) + .scaleEffect(isHovered ? 0.8 : 1.0) + } + } + .onTapGesture { + withAnimation(.smooth()) { + configuration.isOn.toggle() + } + } + configuration.label + } + .onHover(perform: { hovering in + self.isHovered = hovering + }) + } +} + struct InfoButton: View { @State private var isPopoverPresented: Bool = false @@ -192,10 +225,40 @@ struct UninstallButton: ButtonStyle { .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 RescanButton: ButtonStyle { + + func makeBody(configuration: Configuration) -> some View { + HStack(alignment: .center, spacing: 10) { + Image(systemName: "arrow.counterclockwise.circle") + .foregroundColor(.white) + + Divider() + .frame(height: 24) + .foregroundColor(.white) + .opacity(0.5) + + configuration.label + .foregroundColor(.white) + + } + .frame(height: 24) + .frame(minWidth: 75) + .padding(.horizontal) + .padding(.vertical, 4) + .background(configuration.isPressed ? Color("button").opacity(0.8) : Color("button")) .cornerRadius(8) .onHover { over in if over { @@ -475,68 +538,6 @@ struct AnimatedSearchStyle: TextFieldStyle { - -struct SimpleSearchStyle: TextFieldStyle { - @State private var isHovered = false - @FocusState private var isFocused: Bool - @State var icon: Image? - @State var trash: Bool = false - @Binding var text: String - @EnvironmentObject var appState: AppState - @AppStorage("settings.general.mini") private var mini: Bool = false - - func _body(configuration: TextField) -> some View { - - HStack(spacing: 5) { - - HStack(spacing: 5) { - if icon != nil { - icon - .foregroundColor(Color("mode").opacity(0.5)) - } - - configuration - .font(.title3) - .textFieldStyle(PlainTextFieldStyle()) - - - if trash && text != "" { - Image(systemName: "xmark") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 10, height: 10) - .padding(.trailing, 5) - .foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5)) - .onTapGesture { - text = "" - } - .help("Clear search") - } - } - - } - .padding(6) - .overlay( - RoundedRectangle(cornerRadius: 6) - .strokeBorder(Color("mode").opacity(0.2), lineWidth: 1) - .allowsHitTesting(false) - ) - .onHover { hovering in - withAnimation(Animation.easeInOut(duration: 0.15)) { - self.isHovered = hovering - self.isFocused = true - } - } - .focused($isFocused) - .onAppear { - updateOnMain { - self.isFocused = false - } - } - } -} - - struct GlassEffect: NSViewRepresentable { var material: NSVisualEffectView.Material // Choose the material you want var blendingMode: NSVisualEffectView.BlendingMode // Choose the blending mode diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index 7c2a492..9a8b195 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -386,6 +386,9 @@ func removeApp(appState: AppState, withId id: UUID) { if brew { caskCleanup(app: appState.appInfo.appName) } + + appState.appInfo = AppInfo.empty + } } diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index cbb73f3..822c005 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -104,7 +104,7 @@ struct PearcleanerApp: App { // Enable menubar item if menubarEnabled { MenuBarExtraManager.shared.addMenuBarExtra(withView: { - MenuBarMiniAppView(search: $search, showPopover: $showPopover) + MiniAppView(search: $search, showPopover: $showPopover) .environmentObject(locations) .environmentObject(appState) .environmentObject(fsm) diff --git a/Pearcleaner/Resources/Assets.xcassets/button.colorset/Contents.json b/Pearcleaner/Resources/Assets.xcassets/button.colorset/Contents.json index 090caaa..4ac4eda 100644 --- a/Pearcleaner/Resources/Assets.xcassets/button.colorset/Contents.json +++ b/Pearcleaner/Resources/Assets.xcassets/button.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "display-p3", "components" : { "alpha" : "1.000", - "blue" : "0xFF", - "green" : "0x83", - "red" : "0x24" + "blue" : "0xE8", + "green" : "0x7D", + "red" : "0x4D" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "display-p3", "components" : { "alpha" : "1.000", - "blue" : "0xFF", - "green" : "0x83", - "red" : "0x24" + "blue" : "0xBE", + "green" : "0x67", + "red" : "0x3F" } }, "idiom" : "universal" diff --git a/Pearcleaner/Settings/Interface.swift b/Pearcleaner/Settings/Interface.swift index eaad082..efbb7db 100644 --- a/Pearcleaner/Settings/Interface.swift +++ b/Pearcleaner/Settings/Interface.swift @@ -428,7 +428,7 @@ struct InterfaceSettingsTab: View { .onChange(of: menubarEnabled) { newVal in if newVal { MenuBarExtraManager.shared.addMenuBarExtra(withView: { - MenuBarMiniAppView(search: $search, showPopover: $showPopover) + MiniAppView(search: $search, showPopover: $showPopover) .environmentObject(locations) .environmentObject(appState) .environmentObject(fsm) diff --git a/Pearcleaner/Views/AppListItems.swift b/Pearcleaner/Views/AppListItems.swift index 8497113..96baf76 100644 --- a/Pearcleaner/Views/AppListItems.swift +++ b/Pearcleaner/Views/AppListItems.swift @@ -25,7 +25,7 @@ struct AppListItems: View { let itemId = UUID() let appInfo: AppInfo var isSelected: Bool { - appState.appInfo.id == appInfo.id + appState.appInfo.path == appInfo.path } @State private var hoveredItemPath: URL? = nil var body: some View { @@ -39,7 +39,8 @@ struct AppListItems: View { Image(nsImage: appIcon) .resizable() .aspectRatio(contentMode: .fit) - .frame(width: (isHovered || isSelected) ? 32 : 30, height: (isHovered || isSelected) ? 32 : 30) + .frame(width: 30) +// .frame(width: (isHovered || isSelected) ? 32 : 30, height: (isHovered || isSelected) ? 32 : 30) .clipShape(RoundedRectangle(cornerRadius: 8)) // .shadow(color: Color("pop").opacity(0.8), radius: isSelected ? 2 : 0) } diff --git a/Pearcleaner/Views/AppsListView.swift b/Pearcleaner/Views/AppsListView.swift index 80f5a6f..f51f406 100644 --- a/Pearcleaner/Views/AppsListView.swift +++ b/Pearcleaner/Views/AppsListView.swift @@ -30,13 +30,13 @@ struct AppsListView: View { SectionView(title: "System", count: filteredSystemApps.count, apps: filteredSystemApps, search: $search, showPopover: $showPopover) } } -// .padding(.horizontal) .padding(.top, !mini ? 4 : 0) } .scrollIndicators(.never) } } + struct SectionView: View { var title: String var count: Int @@ -51,7 +51,6 @@ struct SectionView: View { ForEach(apps, id: \.self) { appInfo in AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) if appInfo != apps.last { -// Divider().padding(.horizontal, 5) } } } @@ -59,6 +58,72 @@ struct SectionView: View { } + +struct Header: View { + let title: String + let count: Int + @State private var hovered = false + @EnvironmentObject var appState: AppState + @EnvironmentObject var locations: Locations + @EnvironmentObject var fsm: FolderSettingsManager + @Binding var showPopover: Bool + @AppStorage("settings.general.glass") private var glass: Bool = true + + + var body: some View { + HStack { + Text("\(title)").foregroundStyle(Color("mode")).opacity(0.5) + + Text("\(count)") + .font(.system(size: 10)) + .monospacedDigit() + .frame(minWidth: count > 99 ? 30 : 24, minHeight: 17) + .background(Color("mode").opacity(0.1)) + .clipShape(.capsule) + .padding(.leading, 2) + + Spacer() + + if hovered { + Text("REFRESH") + .font(.system(size: 10)) + .monospaced() + .foregroundStyle(Color("mode").opacity(0.8)) + } + + } + .onHover { hovering in + withAnimation() { + hovered = hovering + } + } + .onTapGesture { + withAnimation { + // Refresh Apps list + appState.reload.toggle() + showPopover = false + let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + appState.sortedApps = sortedApps + appState.reload.toggle() + } + } + } + .frame(minHeight: 20) + .help("Click header or ⌘+R to refresh apps list") + .padding(5) + } +} + + + + + + + + + + //List { // Section { // ForEach(filteredUserApps, id:\.self) { element in diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index 6c0b8dc..8bea7ca 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -25,7 +25,7 @@ struct FilesView: View { var regularWin: Bool @State private var elapsedTime = 0 @State private var timer: Timer? = nil - + var body: some View { @@ -80,25 +80,25 @@ struct FilesView: View { } } else { // Titlebar - if !regularWin { - HStack(spacing: 0) { + HStack(spacing: 0) { - Spacer() + Spacer() - Button("Close") { - updateOnMain { - appState.appInfo = AppInfo.empty - search = "" - appState.currentView = .apps - showPopover = false - } + Button("Close") { + updateOnMain { + appState.appInfo = AppInfo.empty + search = "" + appState.currentView = .apps + showPopover = false } - .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close")) } - .padding([.horizontal, .top], 5) + .buttonStyle(SimpleButtonStyle(icon: "x.circle", iconFlip: "x.circle.fill", help: "Close")) } + .padding(.top, 6) + .padding(.trailing, (mini || menubarEnabled) ? 6 : 0) + VStack(spacing: 0) { - + // Main Group HStack(alignment: .center) { @@ -149,7 +149,7 @@ struct FilesView: View { } .padding(.horizontal, 20) .padding(.top, 0) - + } @@ -163,6 +163,8 @@ struct FilesView: View { } } )) + .toggleStyle(SimpleCheckboxToggleStyle()) + .help("All checkboxes") Spacer() @@ -244,9 +246,11 @@ struct FilesView: View { let iconImage = fileIcon.map(Image.init(nsImage:)) VStack { FileDetailsItem(size: fileSize, sizeL: fileSizeL, icon: iconImage, path: path) - if index < sort.count - 1 { - Divider().padding(.leading, 40).opacity(0.5) - } + .padding(.vertical, 5) +// .padding(.leading, 40) +// if index < sort.count - 1 { +// Divider().padding(.leading, 40).opacity(0.5) +// } } } } @@ -269,25 +273,27 @@ struct FilesView: View { Button("\(sizeType == "Logical" ? totalSelectedSize.logical : sizeType == "Finder" ? totalSelectedSize.finder : totalSelectedSize.real)") { Task { - updateOnMain { - search = "" - if !regularWin { - appState.currentView = .apps - showPopover = false - } else { - appState.currentView = .empty + if appState.selectedItems.count == appState.appInfo.fileSize.keys.count { + updateOnMain { + search = "" + if mini || menubarEnabled { + 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() - } +// updateOnMain { +// appState.currentView = mini ? .apps : .empty +// } if sentinel { launchctl(load: true) } @@ -300,15 +306,20 @@ struct FilesView: View { removeApp(appState: appState, withId: appState.appInfo.id) } else { // Add deleted appInfo object to trashed array - appState.appInfo.fileSize = [:] appState.trashedFiles.append(appState.appInfo) - // Clear out appInfoStore object + // Clear out appInfoStore object (Used for leftover file search) if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) { appState.appInfoStore[index] = .empty } + + updateOnMain { + // Remove items from the list + appState.appInfo.fileSize = appState.appInfo.fileSize.filter { !appState.selectedItems.contains($0.key) } + // Update the selectedFiles to remove references that are no longer present + appState.selectedItems.removeAll() + } } - appState.appInfo = AppInfo.empty } } @@ -320,8 +331,6 @@ struct FilesView: View { .padding(.top) -// Spacer() - } } @@ -360,7 +369,7 @@ struct FileDetailsItem: View { } } )) - + .toggleStyle(SimpleCheckboxToggleStyle()) .disabled(self.path.path.contains(".Trash")) if let appIcon = icon { @@ -369,8 +378,6 @@ struct FileDetailsItem: View { .aspectRatio(contentMode: .fit) .frame(width: 30, height: 30) .clipShape(RoundedRectangle(cornerRadius: 8)) - .brightness(isHovered ? 0.2 : 0) - .shadow(color: Color("mode"), radius: isHovered ? 2 : 0) } @@ -382,6 +389,17 @@ struct FileDetailsItem: View { .lineLimit(1) .truncationMode(.tail) .help(path.lastPathComponent) + .overlay{ + if (isHovered) { + VStack { + Spacer() + RoundedRectangle(cornerRadius: 10) + .fill(Color("mode").opacity(0.5)) + .frame(height: 1.5) + .offset(y: 3) + } + } + } if isNested(path: path) { InfoButton(text: "Application file is nested within subdirectories. To prevent deleting incorrect folders, Pearcleaner will leave these alone. You may manually delete the remaining folders if required.", color: nil, label: "") @@ -398,7 +416,17 @@ struct FileDetailsItem: View { .truncationMode(.tail) .opacity(0.5) .help(path.path) + } + .onTapGesture { + NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) + } + .onHover { hovering in + withAnimation(Animation.easeIn(duration: 0.2)) { + self.isHovered = hovering + } + } + Spacer() @@ -409,14 +437,6 @@ struct FileDetailsItem: View { Text("\(displaySize)") } - .onHover { hovering in - withAnimation(Animation.easeIn(duration: 0.2)) { - self.isHovered = hovering - } - } - .onTapGesture { - NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) - } .contextMenu { if path.pathExtension == "app" { Button("Open \(path.deletingPathExtension().lastPathComponent)") { diff --git a/Pearcleaner/Views/MenuBarMiniAppView.swift b/Pearcleaner/Views/MenuBarMiniAppView.swift deleted file mode 100644 index 95f6143..0000000 --- a/Pearcleaner/Views/MenuBarMiniAppView.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// MenuBarAppsListView.swift -// Pearcleaner -// -// Created by Alin Lupascu on 3/16/24. -// - -import Foundation -import SwiftUI - -struct MenuBarMiniAppView: View { - @Environment(\.colorScheme) var colorScheme - @EnvironmentObject var appState: AppState - @EnvironmentObject var locations: Locations - @EnvironmentObject var themeSettings: ThemeSettings - @State private var animateGradient: Bool = false - @Binding var search: String - @State private var showSys: Bool = true - @State private var showUsr: Bool = true - @AppStorage("settings.general.mini") private var mini: Bool = false - @AppStorage("settings.general.glass") private var glass: Bool = true - @AppStorage("settings.general.popover") private var popoverStay: Bool = true - @Binding var showPopover: Bool - @State private var showMenu = false - - var body: some View { - - var filteredApps: [AppInfo] { - if search.isEmpty { - return appState.sortedApps - } else { - return appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) } - } - } - - ZStack { - HStack(spacing: 0){ - - if appState.reload { - VStack { - Spacer() - ProgressView("Refreshing app list") - Spacer() - } - .padding(.vertical) - } else { - VStack(alignment: .center) { - - AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps).padding(.top, 8) - - HStack(spacing: 10) { - - Button("Leftover Files") { - showMenu = false - withAnimation(.easeInOut(duration: 0.5)) { - showPopover = false - updateOnMain() { - appState.appInfo = .empty - appState.selectedZombieItems = [] - if appState.zombieFile.fileSize.keys.count == 0 { - appState.currentView = .zombie - appState.showProgress.toggle() - showPopover.toggle() - reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) - } else { - appState.currentView = .zombie - showPopover.toggle() - } - } - - } - } - .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files")) - .padding(.leading, 10) - - SearchBarMiniBottom(search: $search) - - Button("More") { - self.showMenu.toggle() - } - .padding(.trailing, 10) - .buttonStyle(SimpleButtonStyle(icon: "ellipsis.circle", help: "More")) - .popover(isPresented: $showMenu) { - VStack(alignment: .leading) { - - if #available(macOS 14.0, *) { - SettingsLink{ - Label("Settings", systemImage: "gear") - } - .buttonStyle(SimpleButtonStyle(icon: "gear", label: "Settings", help: "Settings")) - } else { - Button("Settings") { - NSApp.sendAction(Selector(("showPreferencesWindow:")), to: NSApp.delegate, from: nil) - showMenu = false - } - .buttonStyle(SimpleButtonStyle(icon: "gear", label: "Settings", help: "Settings")) - } - - Button("Quit") { - NSApp.terminate(nil) - } - .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", label: "Quit Pearcleaner", help: "Quit Pearcleaner")) - } - .padding() - .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) - - } - - } - .padding(.top, 5) - - - } - .padding(.vertical, 8) - .padding(.bottom, 8) - } - - - } - } - .frame(minWidth: 300, minHeight: 370) - .edgesIgnoringSafeArea(.all) - .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) - .transition(.opacity) - .popover(isPresented: $showPopover, arrowEdge: .leading) { - VStack { - if appState.currentView == .files { - FilesView(showPopover: $showPopover, search: $search, regularWin: false) - .id(appState.appInfo.id) - } else if appState.currentView == .zombie { - ZombieView(showPopover: $showPopover, search: $search, regularWin: false) - .id(appState.appInfo.id) - } - - } - .interactiveDismissDisabled(popoverStay) - .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) - .frame(width: 650, height: 500) - - } - } -} diff --git a/Pearcleaner/Views/MiniMode.swift b/Pearcleaner/Views/MiniMode.swift index 2454ca0..d48b61d 100644 --- a/Pearcleaner/Views/MiniMode.swift +++ b/Pearcleaner/Views/MiniMode.swift @@ -28,56 +28,17 @@ struct MiniMode: View { VStack(spacing: 0) { Group { if appState.currentView == .empty { - TopBarMini(search: $search, showPopover: $showPopover) MiniEmptyView(showPopover: $showPopover) } else { - TopBarMini(search: $search, showPopover: $showPopover) MiniAppView(search: $search, showPopover: $showPopover) - } } .transition(.opacity) } - .popover(isPresented: $showPopover, arrowEdge: .trailing) { - VStack { - if appState.currentView == .files { - FilesView(showPopover: $showPopover, search: $search, regularWin: false) - .id(appState.appInfo.id) - } else if appState.currentView == .zombie { - ZombieView(showPopover: $showPopover, search: $search, regularWin: false) - .id(appState.appInfo.id) - } - - } - .interactiveDismissDisabled(popoverStay) -// .background(Color("pop")) -// .background(backgroundView(themeSettings: themeSettings, glass: false).padding(-80)) - .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) -// .background( -// Group { -// if glass { -// backgroundView(themeSettings: themeSettings).padding(-80) -//// GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) -// } else { -// Rectangle() -// .fill(Color("pop")) -// .padding(-80) -// } -// } -// ) - .frame(width: 650, height: 550) - - } - - } .frame(minWidth: 300, minHeight: 345) .edgesIgnoringSafeArea(.all) .background(backgroundView(themeSettings: themeSettings, glass: glass)) -// .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) - // MARK: Background for whole app - // .background(Color("bg").opacity(1)) - // .background(VisualEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all)) } } @@ -95,6 +56,7 @@ struct MiniEmptyView: View { @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.animateLogo") private var animateLogo: Bool = true @Binding var showPopover: Bool + @State private var animationStart = false var body: some View { VStack(alignment: .center) { @@ -102,7 +64,7 @@ struct MiniEmptyView: View { Spacer() if #available(macOS 14, *) { - if animateLogo { + if animateLogo && animationStart { LinearGradient(gradient: Gradient(colors: [.green, .orange]), startPoint: .leading, endPoint: .trailing) .mask( Image(systemName: "plus.square.dashed") @@ -144,37 +106,29 @@ struct MiniEmptyView: View { ) } - - Text("Drop an app here") .font(.title3) + .opacity(0.7) + + Text("Click for apps list") + .font(.footnote) .padding(.bottom, 25) .opacity(0.5) - Spacer() - - -// if appState.isReminderVisible { -// Text("CMD + Z to undo") -// .font(.title2) -// .foregroundStyle(Color("mode").opacity(0.5)) -// .fontWeight(.medium) -// .onAppear { -// DispatchQueue.main.asyncAfter(deadline: .now() + 2) { -// withAnimation { -// updateOnMain { -// appState.isReminderVisible = false -// } -// } -// } -// } -// } -// -// Spacer() } + .onTapGesture { + withAnimation(.easeInOut(duration: 0.5)) { + appState.currentView = .apps + } + } + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + animationStart = true + } + } } } @@ -185,52 +139,58 @@ struct MiniEmptyView: View { struct MiniAppView: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject var appState: AppState + @EnvironmentObject var locations: Locations + @EnvironmentObject var themeSettings: ThemeSettings @State private var animateGradient: Bool = false @Binding var search: String @State private var showSys: Bool = true @State private var showUsr: Bool = true @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.glass") private var glass: Bool = true + @AppStorage("settings.general.popover") private var popoverStay: Bool = true + @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false + @AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280 @Binding var showPopover: Bool - + @State private var showMenu = false + var body: some View { - - var filteredApps: [AppInfo] { - if search.isEmpty { - return appState.sortedApps - } else { - return appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) } - } - } - + + ZStack { - HStack(spacing: 0){ - - if appState.reload { - VStack { - Spacer() - ProgressView("Refreshing app list") - Spacer() - } - .padding(.vertical) - } else { - VStack(alignment: .center) { - AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps) - - if appState.currentView != .empty { - SearchBarMiniBottom(search: $search) - .padding(.horizontal) - .padding(.top, 5) - } - } - .padding(.bottom) + if appState.reload { + VStack { + Spacer() + ProgressView("Refreshing app list") + Spacer() } - - + .padding(.vertical) + } else { + Searchbar(glass: glass, sidebarWidth: sidebarWidth, menubarEnabled: menubarEnabled, mini: mini, search: $search, showPopover: $showPopover) } + } .transition(.opacity) + .frame(minWidth: 300, minHeight: 370) + .edgesIgnoringSafeArea(.all) + .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) + .transition(.opacity) + .popover(isPresented: $showPopover, arrowEdge: .trailing) { + VStack { + if appState.currentView == .files { + FilesView(showPopover: $showPopover, search: $search, regularWin: false) + .id(appState.appInfo.id) + } else if appState.currentView == .zombie { + ZombieView(showPopover: $showPopover, search: $search, regularWin: false) + .id(appState.appInfo.id) + } + + } + .interactiveDismissDisabled(popoverStay) + .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) + .frame(width: 650, height: 500) + + } } } diff --git a/Pearcleaner/Views/RegularMode.swift b/Pearcleaner/Views/RegularMode.swift index a896eb0..4fbf068 100644 --- a/Pearcleaner/Views/RegularMode.swift +++ b/Pearcleaner/Views/RegularMode.swift @@ -11,26 +11,22 @@ import SwiftUI struct RegularMode: View { @EnvironmentObject var appState: AppState @EnvironmentObject var themeSettings: ThemeSettings + @EnvironmentObject var locations: Locations @AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280 + @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false + @AppStorage("settings.general.mini") private var mini: Bool = false @Binding var search: String @State private var showSys: Bool = true @State private var showUsr: Bool = true @Binding var showPopover: Bool - - - var filteredApps: [AppInfo] { - if search.isEmpty { - return appState.sortedApps - } else { - return appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) } - } - } + @State private var showMenu = false var body: some View { HStack(alignment: .center, spacing: 0) { + // App List HStack(spacing: 0){ @@ -43,26 +39,10 @@ struct RegularMode: View { .frame(width: sidebarWidth) .padding(.vertical) } else { - VStack(alignment: .center) { - - VStack(alignment: .center, spacing: 20) { - HStack { - SearchBarMiniBottom(search: $search) - .padding(.horizontal) - - } - } - .padding(.top, 20) - - - AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps) - - } - .frame(width: sidebarWidth) - .padding(.vertical, 7) + Searchbar(glass: glass, sidebarWidth: sidebarWidth, menubarEnabled: menubarEnabled, mini: mini, search: $search, showPopover: $showPopover) + .frame(width: sidebarWidth) } - } .background(backgroundView(themeSettings: themeSettings, darker: true, glass: glass)) .transition(.opacity) @@ -73,22 +53,21 @@ struct RegularMode: View { // Details View - VStack(spacing: 0) { + HStack(spacing: 0) { + Spacer() Group { if appState.currentView == .empty || appState.currentView == .apps { - TopBar(showPopover: $showPopover) AppDetailsEmptyView(showPopover: $showPopover) } else if appState.currentView == .files { - TopBar(showPopover: $showPopover) FilesView(showPopover: $showPopover, search: $search, regularWin: true) .id(appState.appInfo.id) } else if appState.currentView == .zombie { - TopBar(showPopover: $showPopover) ZombieView(showPopover: $showPopover, search: $search, regularWin: true) .id(appState.appInfo.id) } } .transition(.opacity) + Spacer() } .zIndex(2) .background(backgroundView(themeSettings: themeSettings)) @@ -109,13 +88,14 @@ struct AppDetailsEmptyView: View { @EnvironmentObject var locations: Locations @AppStorage("settings.general.animateLogo") private var animateLogo: Bool = true @Binding var showPopover: Bool + @State private var animationStart = false var body: some View { VStack(alignment: .center) { Spacer() if #available(macOS 14, *) { - if animateLogo { + if animateLogo && animationStart { PearDropView() .phaseAnimator([false, true]) { wwdc24, chromaRotate in wwdc24 @@ -139,75 +119,10 @@ struct AppDetailsEmptyView: View { .padding(.bottom, 25) .opacity(0.5) } - } -} - - -struct SearchBar: View { - @Binding var search: String - - var body: some View { - HStack { - TextField("Search", text: $search) - .textFieldStyle(SimpleSearchStyle(icon: Image(systemName: "magnifyingglass"),trash: true, text: $search)) - } - .frame(height: 30) - } -} - - -struct Header: View { - let title: String - let count: Int - @State private var hovered = false - @EnvironmentObject var appState: AppState - @EnvironmentObject var locations: Locations - @EnvironmentObject var fsm: FolderSettingsManager - @Binding var showPopover: Bool - @AppStorage("settings.general.glass") private var glass: Bool = true - - - var body: some View { - HStack { - Text("\(title)").foregroundStyle(Color("mode")).opacity(0.5) - - Text("\(count)") - .font(.system(size: 10)) - .monospacedDigit() - .frame(minWidth: count > 99 ? 30 : 24, minHeight: 17) - .background(Color("mode").opacity(0.1)) - .clipShape(.capsule) - .padding(.leading, 2) - - Spacer() - - if hovered { - Text("REFRESH") - .font(.system(size: 10)) - .monospaced() - .foregroundStyle(Color("mode").opacity(0.8)) - } - - } - .onHover { hovering in - withAnimation() { - hovered = hovering + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + animationStart = true } } - .onTapGesture { - withAnimation { - // Refresh Apps list - appState.reload.toggle() - showPopover = false - let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - appState.sortedApps = sortedApps - appState.reload.toggle() - } - } - } - .frame(minHeight: 20) - .help("Click header or ⌘+R to refresh apps list") - .padding(5) } } diff --git a/Pearcleaner/Views/Searchbar.swift b/Pearcleaner/Views/Searchbar.swift new file mode 100644 index 0000000..2d84263 --- /dev/null +++ b/Pearcleaner/Views/Searchbar.swift @@ -0,0 +1,213 @@ +// +// Searchbar.swift +// Pearcleaner +// +// Created by Alin Lupascu on 4/26/24. +// + +import SwiftUI + +struct Searchbar: View { + @EnvironmentObject var appState: AppState + @EnvironmentObject var themeSettings: ThemeSettings + @EnvironmentObject var locations: Locations + var glass: Bool + var sidebarWidth: Double + var menubarEnabled: Bool + var mini: Bool + @Binding var search: String + @Binding var showPopover: Bool + @State private var showMenu = false + @State private var showSys: Bool = true + @State private var showUsr: Bool = true + + var body: some View { + VStack(alignment: .center, spacing: 0) { + Spacer() + .frame(height: 10) + + AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps) + .padding(.top, 25) + + Divider() + + HStack(spacing: 10) { + + + SearchBar(search: $search, darker: (mini || menubarEnabled) ? false : true, glass: glass) + + + if search.isEmpty { + Button("More") { + self.showMenu.toggle() + } + .buttonStyle(SimpleButtonStyle(icon: "ellipsis.circle", help: "More")) + .popover(isPresented: $showMenu) { + VStack(alignment: .leading) { + + if mini && !menubarEnabled { + Button("") { + withAnimation(.easeInOut(duration: 0.5)) { + appState.currentView = .empty + appState.appInfo = AppInfo.empty + showPopover = false + } + } + .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", label: "Drop Target", help: "Drop Target")) + } + + + Button("Leftover Files") { + showMenu = false + withAnimation(.easeInOut(duration: 0.5)) { + showPopover = false + appState.appInfo = .empty + appState.selectedZombieItems = [] + if appState.zombieFile.fileSize.keys.isEmpty { + appState.currentView = .zombie + appState.showProgress.toggle() + showPopover.toggle() + reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) + } else { + appState.currentView = .zombie + showPopover.toggle() + } + } + } + .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Leftover Files", help: "Leftover Files", size: 5)) + + + if #available(macOS 14.0, *) { + SettingsLink {} + .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Settings", help: "Settings", size: 5)) + } else { + Button("Settings") { + NSApp.sendAction(Selector(("showPreferencesWindow:")), to: NSApp.delegate, from: nil) + showMenu = false + } + .buttonStyle(SimpleButtonStyle(icon: "circle.fill", label: "Settings", help: "Settings", size: 5)) + } + + if menubarEnabled { + Button("Quit") { + NSApp.terminate(nil) + } + .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", label: "Quit Pearcleaner", help: "Quit Pearcleaner")) + } + + } + .padding() + .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) + + } + } + + + + } + .padding(.horizontal, search.isEmpty ? 10 : 5) + .padding(.vertical, 5) + } + } + + private var filteredApps: [AppInfo] { + if search.isEmpty { + return appState.sortedApps + } else { + return appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) } + } + } +} + + + + +struct SimpleSearchStyle: TextFieldStyle { + @State private var isHovered = false + @FocusState private var isFocused: Bool + @State var trash: Bool = false + @Binding var text: String + @State var darker: Bool = false + @State var glass: Bool = false + @State var padding: CGFloat = 5 + @EnvironmentObject var appState: AppState + @EnvironmentObject var themeSettings: ThemeSettings + @AppStorage("settings.general.mini") private var mini: Bool = false + + func _body(configuration: TextField) -> some View { + + ZStack { + RoundedRectangle(cornerRadius: 8) + .fill(darker ? themeSettings.themeColor.darker(by: 5) : themeSettings.themeColor) + .allowsHitTesting(false) + .frame(height: 30) + .opacity(glass ? 0.0 : 1.0) + + + ZStack { + if text.isEmpty { + HStack { + Spacer() + Text(isFocused ? "Type to search" : "Click to search") + .font(.subheadline) + .foregroundColor(Color("mode").opacity(0.2)) + Spacer() + } + } + HStack { + configuration + .font(.title3) + .textFieldStyle(PlainTextFieldStyle()) + + Spacer() + + if trash && text != "" { + Button("") { + text = "" + } + .buttonStyle(SimpleButtonStyle(icon: "delete.left.fill", help: "Clear text", size: 14, padding: padding)) + } + } + + } + .padding(.horizontal, 8) + + } + .onHover { hovering in + withAnimation(Animation.easeInOut(duration: 0.15)) { + self.isHovered = hovering + self.isFocused = true + } + } + .focused($isFocused) + .onAppear { + updateOnMain { + self.isFocused = true + } + } + } +} + +// Hide blinking textfield caret +extension NSTextView { + open override var frame: CGRect { + didSet { + insertionPointColor = .clear + } + } +} + +struct SearchBar: View { + @Binding var search: String + @State var darker: Bool = false + @State var glass: Bool = false + @State var padding: CGFloat = 5 + @EnvironmentObject var appState: AppState + + var body: some View { + HStack { + TextField("", text: $search) + .textFieldStyle(SimpleSearchStyle(trash: true, text: $search, darker: darker, glass: glass, padding: padding)) + } + } +} diff --git a/Pearcleaner/Views/TopBar.swift b/Pearcleaner/Views/TopBar.swift deleted file mode 100644 index e6ca8ae..0000000 --- a/Pearcleaner/Views/TopBar.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// TopBar.swift -// Pearcleaner -// -// Created by Alin Lupascu on 11/10/23. -// - -import Foundation -import SwiftUI - -struct TopBar: View { - @AppStorage("displayMode") var displayMode: DisplayMode = .system - @AppStorage("settings.general.glass") private var glass: Bool = false - @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false - @AppStorage("settings.general.mini") private var mini: Bool = false - @EnvironmentObject var appState: AppState - @Binding var showPopover: Bool - @EnvironmentObject var locations: Locations - - var body: some View { - HStack(alignment: .center, spacing: 0) { - -// Spacer() -// -// if appState.isReminderVisible { -// Text("CMD + Z to undo") -// .font(.title2) -// .foregroundStyle(Color("mode").opacity(0.5)) -// .fontWeight(.medium) -// .onAppear { -// DispatchQueue.main.asyncAfter(deadline: .now() + 2) { -// withAnimation { -// appState.isReminderVisible = false -// } -// } -// } -// } - - Spacer() - - HStack() { - Spacer() - - if appState.currentView == .zombie { - Button("Rescan") { - updateOnMain { - appState.zombieFile = .empty - appState.showProgress.toggle() - reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) - } - } - .buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle.fill", help: "Rescan files")) - } - - if appState.currentView != .zombie { - Button("") { - withAnimation(.easeInOut(duration: 0.5)) { - updateOnMain { - appState.appInfo = .empty - appState.selectedZombieItems = [] - if appState.zombieFile.fileSize.keys.count == 0 { - appState.currentView = .zombie - appState.showProgress.toggle() - showPopover.toggle() - reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) - } else { - appState.currentView = .zombie - showPopover.toggle() - } - } - - } - } - .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files")) - } - - - if appState.currentView == .files || appState.currentView == .zombie { - Button("") { - withAnimation(.easeInOut(duration: 0.5)) { - appState.currentView = .empty - appState.appInfo = AppInfo.empty - } - } - .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", help: "Drop Target")) - - } - - - } - - - } - .padding(6) - } -} diff --git a/Pearcleaner/Views/TopBarMini.swift b/Pearcleaner/Views/TopBarMini.swift deleted file mode 100644 index ea8a084..0000000 --- a/Pearcleaner/Views/TopBarMini.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// TopBarMini.swift -// Pearcleaner -// -// Created by Alin Lupascu on 11/14/23. -// - -import Foundation -import SwiftUI - -struct TopBarMini: View { - @AppStorage("displayMode") var displayMode: DisplayMode = .system - @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false - @AppStorage("settings.general.mini") private var mini: Bool = false - @Binding var search: String - @Binding var showPopover: Bool - @EnvironmentObject var appState: AppState - @EnvironmentObject var locations: Locations - - var body: some View { - HStack(alignment: .center, spacing: 0) { - - Spacer() - - if appState.currentView == .empty { - - HStack { - Spacer() - - Button("") { - withAnimation(.easeInOut(duration: 0.5)) { - showPopover = false - updateOnMain { - if appState.zombieFile.fileSize.keys.count == 0 { - appState.currentView = .zombie - appState.showProgress.toggle() - showPopover.toggle() - reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) - } else { - appState.currentView = .zombie - showPopover.toggle() - } - } - - } - } - .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files")) - - Button("") { - withAnimation(.easeInOut(duration: 0.5)) { - appState.currentView = .apps - } - } - .buttonStyle(SimpleButtonStyle(icon: "square.grid.3x3.square", help: "Apps List")) - - - } - - } - - if appState.currentView != .empty { - HStack { - Spacer() - - Button("") { - withAnimation(.easeInOut(duration: 0.5)) { - showPopover = false - updateOnMain { - appState.appInfo = .empty - appState.selectedZombieItems = [] - if appState.zombieFile.fileSize.keys.count == 0 { - appState.currentView = .zombie - appState.showProgress.toggle() - showPopover.toggle() - reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) - } else { - appState.currentView = .zombie - showPopover.toggle() - } - } - - } - } - .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files")) - - Button("") { - withAnimation(.easeInOut(duration: 0.5)) { - appState.currentView = .empty - appState.appInfo = AppInfo.empty - showPopover = false - } - } - .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", help: "Drop Target")) - } - } - - - } - .padding(.horizontal, 10) - .padding(.top, 10) - .padding(.bottom, 5) - } -} - - -struct SearchBarMini: View { - @Binding var search: String - - var body: some View { - HStack { - TextField("Search", text: $search) - .textFieldStyle(AnimatedSearchStyle(text: $search)) - } - .frame(height: 20) - } -} - -struct SearchBarMiniBottom: View { - @Binding var search: String - @EnvironmentObject var appState: AppState - - var body: some View { - HStack { - TextField(" Search", text: $search) - .textFieldStyle(SimpleSearchStyle(trash: true, text: $search)) - } - } -} diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index e40dfbe..d326701 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -13,6 +13,7 @@ struct ZombieView: View { @EnvironmentObject var locations: Locations @State private var showPop: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false + @AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false @AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true @@ -108,31 +109,22 @@ struct ZombieView: View { } } else { // Titlebar - if !regularWin { - HStack(spacing: 0) { - Spacer() + HStack(spacing: 0) { + Spacer() - Button("Rescan") { - updateOnMain { - appState.zombieFile = .empty - appState.showProgress.toggle() - reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) - } + Button("Close") { + updateOnMain { + appState.appInfo = AppInfo.empty + search = "" + appState.currentView = .apps + showPopover = false } - .buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle.fill", help: "Rescan files")) - - Button("Close") { - updateOnMain { - appState.appInfo = AppInfo.empty - search = "" - appState.currentView = .apps - showPopover = false - } - } - .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close")) } - .padding([.horizontal, .top], 5) + .buttonStyle(SimpleButtonStyle(icon: "x.circle", iconFlip: "x.circle.fill", help: "Close")) } + .padding(.top, 6) + .padding(.trailing, (mini || menubarEnabled) ? 6 : 0) + VStack() { // Main Group @@ -141,7 +133,7 @@ struct ZombieView: View { VStack(alignment: .center) { HStack(alignment: .center) { - Image(systemName: "clock.arrow.circlepath") + Image(systemName: "doc.badge.clock.fill") .resizable() .scaledToFit() .frame(width: 50, height: 50) @@ -185,9 +177,11 @@ struct ZombieView: View { } } )) + .toggleStyle(SimpleCheckboxToggleStyle()) + .help("All checkboxes") - SearchBarMiniBottom(search: $searchZ) -// .padding(.top) + + SearchBar(search: $searchZ, darker: true, glass: glass) .padding(.horizontal) @@ -212,11 +206,7 @@ struct ZombieView: View { let iconImage = fileIcon.map(Image.init(nsImage:)) VStack { ZombieFileDetailsItem(size: fileSize, sizeL: fileSizeL, icon: iconImage, path: file) - .padding(.trailing) - - if index < filteredAndSortedFiles.0.count - 1 { - Divider().padding(.leading, 40).opacity(0.5) - } + .padding(.vertical, 5) } } } @@ -226,8 +216,9 @@ struct ZombieView: View { .onChange(of: sizeType) { _ in localKey = UUID() } - .id(localKey) } + .id(localKey) + Spacer() @@ -236,30 +227,45 @@ struct ZombieView: View { Spacer() + Button("Rescan") { + updateOnMain { + appState.zombieFile = .empty + appState.showProgress.toggle() + reversePreloader(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) + } + } + .buttonStyle(RescanButton()) + Button("\(sizeType == "Logical" ? totalSelectedZombieSize.logical : sizeType == "Finder" ? totalSelectedZombieSize.finder : totalSelectedZombieSize.real)") { Task { - updateOnMain { - appState.zombieFile = .empty - search = "" - if !regularWin { - appState.currentView = .apps - showPopover = false - } else { - appState.currentView = .empty + if appState.selectedZombieItems.count == appState.zombieFile.fileSize.keys.count { + updateOnMain { + appState.zombieFile = .empty + search = "" + searchZ = "" + if mini || menubarEnabled { + appState.currentView = .apps + showPopover = false + } else { + appState.currentView = .empty + } } } + let selectedItemsArray = Array(appState.selectedZombieItems) - killApp(appId: appState.appInfo.bundleIdentifier) { - moveFilesToTrash(at: selectedItemsArray) { - withAnimation { - showPopover = false -// updateOnMain { -// appState.isReminderVisible.toggle() -// } - } + moveFilesToTrash(at: selectedItemsArray) { + withAnimation { + showPopover = false } + updateOnMain { + // Remove items from the list + appState.zombieFile.fileSize = appState.zombieFile.fileSize.filter { !appState.selectedZombieItems.contains($0.key) } + // Update the selectedZombieFiles to remove references that are no longer present + appState.selectedZombieItems.removeAll() + } + } } @@ -267,20 +273,16 @@ struct ZombieView: View { } .buttonStyle(UninstallButton(isEnabled: !appState.selectedZombieItems.isEmpty)) .disabled(appState.selectedZombieItems.isEmpty) - .padding(.top) -// Spacer() - } - + .padding(.top) } .transition(.opacity) .padding([.horizontal, .bottom], 20) .padding(.top, !mini ? 10 : 0) } - } } } @@ -309,6 +311,7 @@ struct ZombieFileDetailsItem: View { } } )) + .toggleStyle(SimpleCheckboxToggleStyle()) if let appIcon = icon { appIcon @@ -316,10 +319,9 @@ struct ZombieFileDetailsItem: View { .aspectRatio(contentMode: .fit) .frame(width: 30, height: 30) .clipShape(RoundedRectangle(cornerRadius: 8)) - .brightness(isHovered ? 0.2 : 0) - .shadow(color: Color("mode"), radius: isHovered ? 2 : 0) } + VStack(alignment: .leading, spacing: 5) { HStack(alignment: .center) { @@ -328,6 +330,17 @@ struct ZombieFileDetailsItem: View { .lineLimit(1) .truncationMode(.tail) .help(path.lastPathComponent) + .overlay{ + if (isHovered) { + VStack { + Spacer() + RoundedRectangle(cornerRadius: 10) + .fill(Color("mode").opacity(0.5)) + .frame(height: 1.5) + .offset(y: 3) + } + } + } if let imageView = folderImages(for: path.path) { imageView @@ -341,6 +354,14 @@ struct ZombieFileDetailsItem: View { .opacity(0.5) .help(path.path) } + .onHover { hovering in + withAnimation(Animation.easeIn(duration: 0.2)) { + self.isHovered = hovering + } + } + .onTapGesture { + NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) + } Spacer() @@ -351,14 +372,6 @@ struct ZombieFileDetailsItem: View { Text("\(displaySize)") } - .onHover { hovering in - withAnimation(Animation.easeIn(duration: 0.2)) { - self.isHovered = hovering - } - } - .onTapGesture { - NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) - } .contextMenu { if path.pathExtension == "app" { Button("Open \(path.deletingPathExtension().lastPathComponent)") {