diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index f66728d..31ba752 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -535,7 +535,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 6; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; ENABLE_HARDENED_RUNTIME = YES; @@ -552,7 +552,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.4; + MARKETING_VERSION = 1.5; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -569,7 +569,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 6; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; ENABLE_HARDENED_RUNTIME = YES; @@ -586,7 +586,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.4; + MARKETING_VERSION = 1.5; 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 5649115..4c74e12 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -12,7 +12,7 @@ let home = FileManager.default.homeDirectoryForCurrentUser.path class AppState: ObservableObject { - @Published var showFiles: Bool = false +// @Published var showFiles: Bool = false @Published var appInfo: AppInfo @Published var paths: [URL] = [] @Published var sortedApps: (userApps: [AppInfo], systemApps: [AppInfo]) = ([], []) @@ -22,7 +22,7 @@ class AppState: ObservableObject // @Published var currentMode = CurrentMode.regular @Published var showAlert: Bool = false @Published var sidebar: Bool = true - @Published var showPopover: Bool = false +// @Published var showPopover: Bool = false @Published var isReminderVisible: Bool = false @Published var releases = [Release]() @Published var progressBar: (String, Double) = ("Ready", 0.0) diff --git a/Pearcleaner/Logic/Logic.swift b/Pearcleaner/Logic/Logic.swift index 3faf2eb..3f84b91 100644 --- a/Pearcleaner/Logic/Logic.swift +++ b/Pearcleaner/Logic/Logic.swift @@ -186,8 +186,6 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) { collection.insert(url, at: 0) } - // Only search for extra files if not a webapp - // if !appInfo.webApp { let fileManager = FileManager.default let dispatchGroup = DispatchGroup() let bundleComponents = appInfo.bundleIdentifier.components(separatedBy: ".") @@ -200,6 +198,11 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) { let locations = Locations() for location in locations.apps.paths { + if !fileManager.fileExists(atPath: location) { +// print("Directory does not exist: \(location)") + continue + } + dispatchGroup.enter() // Enter the dispatch group do { @@ -217,7 +220,7 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) { if appInfo.webApp { if itemL.contains(bundleIdentifierL) { if collection.contains(itemURL) { - return + continue } collection.append(itemURL) } @@ -265,20 +268,13 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) { } } - // } - - // if appInfo.webApp { - // updateOnMain { - // appState.paths = collection - // appState.selectedItems = Set(collection) - // } - // } } } + // Move files to trash using applescript/Finder so it asks for user password if needed func moveFilesToTrash(at fileURLs: [URL], completion: @escaping () -> Void = {}) { updateOnBackground { diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index 053c41b..3ce80f6 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -78,8 +78,6 @@ struct LabeledDivider: View { struct AnimatedSearchStyle: TextFieldStyle { @State private var isHovered = false - @State var icon: Image? - @State var trash: Bool = false @Binding var text: String func _body(configuration: TextField) -> some View { @@ -87,51 +85,32 @@ struct AnimatedSearchStyle: TextFieldStyle { HStack(spacing: 5) { if isHovered || !text.isEmpty { - HStack(spacing: 5) { - if icon != nil { - icon - .foregroundColor(Color("AccentColor").opacity(0.5)) - Image(systemName: "chevron.right") - .foregroundColor(Color("AccentColor").opacity(0.3)) - .controlSize(.small) - - } - - configuration - .frame(maxWidth: 300) - .font(.title3) - .textFieldStyle(PlainTextFieldStyle()) - if trash { - Image(systemName: "xmark") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 10, height: 10) - .padding(.trailing, 5) - .foregroundStyle(text.isEmpty ? Color.clear : (isHovered ? Color("AccentColor").opacity(0.8) : Color("AccentColor").opacity(0.5))) - .onTapGesture { - text = "" - } - } - } + configuration + .frame(width: 160) + .frame(height: 15) + .font(.title3) + .textFieldStyle(PlainTextFieldStyle()) + } else { Image(systemName: "magnifyingglass") - .foregroundColor(Color("AccentColor").opacity(0.5)) - .padding(.trailing, 150) - + .resizable() + .scaledToFit() + .frame(width: 18, height: 18) + .foregroundColor(isHovered ? Color("mode") : Color("mode").opacity(0.5)) } } - .padding(6) + .padding(8) .overlay( Group { if isHovered || !text.isEmpty { RoundedRectangle(cornerRadius: 6) - .strokeBorder(Color("AccentColor").opacity(0.4), lineWidth: 1) + .strokeBorder(Color("mode").opacity(0.4), lineWidth: 1) .allowsHitTesting(false) } } ) .onHover { hovering in - withAnimation(Animation.easeIn(duration: 0.15)) { + withAnimation(Animation.easeInOut(duration: 0.4)) { self.isHovered = hovering } } @@ -139,6 +118,8 @@ struct AnimatedSearchStyle: TextFieldStyle { } + + struct SimpleSearchStyle: TextFieldStyle { @State private var isHovered = false @State var icon: Image? diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index 0a9328f..7042670 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -18,17 +18,18 @@ struct PearcleanerApp: App { @AppStorage("displayMode") var displayMode: DisplayMode = .system @AppStorage("settings.general.mini") private var mini: Bool = false @State private var search = "" - + @State private var showPopover: Bool = false + var body: some Scene { WindowGroup { Group { if !mini { - AppListView(search: $search) + AppListView(search: $search, showPopover: $showPopover) .frame(minWidth: 800, minHeight: 500) } else { - MiniMode(search: $search) + MiniMode(search: $search, showPopover: $showPopover) .frame(minWidth: 300, minHeight: 300) } @@ -44,7 +45,7 @@ struct PearcleanerApp: App { let sortedApps = getSortedApps() appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps.systemApps = sortedApps.systemApps - + Task { #if !DEBUG @@ -78,7 +79,6 @@ struct PearcleanerApp: App { } .windowStyle(.hiddenTitleBar) - .windowToolbarStyle(.unified) .windowResizability(.contentMinSize) .commands { AppCommands(appState: appState) diff --git a/Pearcleaner/Views/AppListItems.swift b/Pearcleaner/Views/AppListItems.swift index a239969..3ef142e 100644 --- a/Pearcleaner/Views/AppListItems.swift +++ b/Pearcleaner/Views/AppListItems.swift @@ -11,10 +11,11 @@ import SwiftUI struct AppListItems: View { @EnvironmentObject var appState: AppState // @Binding var reload: Bool -// @Binding var search: String + @Binding var search: String @State private var isHovered = false @Environment(\.colorScheme) var colorScheme @AppStorage("settings.general.mini") private var mini: Bool = false + @Binding var showPopover: Bool let appInfo: AppInfo var isSelected: Bool { @@ -93,11 +94,12 @@ struct AppListItems: View { } .onTapGesture { updateOnMain { + appState.appInfo = .empty appState.appInfo = appInfo findPathsForApp(appState: appState, appInfo: appState.appInfo) withAnimation(Animation.easeIn(duration: 0.4)) { if mini { - appState.showPopover.toggle() + showPopover.toggle() } else { appState.currentView = .files } diff --git a/Pearcleaner/Views/AppListView.swift b/Pearcleaner/Views/AppListView.swift index 4688ada..51b3ac0 100644 --- a/Pearcleaner/Views/AppListView.swift +++ b/Pearcleaner/Views/AppListView.swift @@ -16,6 +16,7 @@ struct AppListView: View { @State private var showUsr: Bool = true // @State private var sidebar: Bool = true @State private var reload: Bool = false + @Binding var showPopover: Bool var filteredUserApps: [AppInfo] { if search.isEmpty { @@ -56,20 +57,20 @@ struct AppListView: View { HStack { SearchBar(search: $search, reload: $reload) - Button("") { - withAnimation(.easeInOut(duration: 0.5)) { - // Refresh Apps list - reload.toggle() - let sortedApps = getSortedApps() - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - appState.sortedApps.userApps = sortedApps.userApps - appState.sortedApps.systemApps = sortedApps.systemApps - reload.toggle() - } - - } - } - .buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh app list", color: Color("mode"))) +// Button("") { +// withAnimation(.easeInOut(duration: 0.5)) { +// // Refresh Apps list +// reload.toggle() +// let sortedApps = getSortedApps() +// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { +// appState.sortedApps.userApps = sortedApps.userApps +// appState.sortedApps.systemApps = sortedApps.systemApps +// reload.toggle() +// } +// +// } +// } +// .buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh app list", color: Color("mode"))) } } .padding(.horizontal) @@ -84,7 +85,7 @@ struct AppListView: View { VStack { Header(title: "User", count: filteredUserApps.count) ForEach(filteredUserApps, id: \.self) { appInfo in - AppListItems(appInfo: appInfo) + AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) if appInfo != filteredUserApps.last { Divider().padding(.horizontal, 5) } @@ -98,7 +99,7 @@ struct AppListView: View { VStack { Header(title: "System", count: filteredSystemApps.count) ForEach(filteredSystemApps, id: \.self) { appInfo in - AppListItems(appInfo: appInfo) + AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) if appInfo != filteredSystemApps.last { Divider().padding(.horizontal, 5) } @@ -135,7 +136,7 @@ struct AppListView: View { AppDetailsEmptyView() } else if appState.currentView == .files { TopBar(reload: $reload) - FilesView() + FilesView(showPopover: $showPopover) .id(appState.appInfo.id) } } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index a008d25..02fdc8b 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -16,7 +16,8 @@ struct FilesView: View { @State private var itemDetails: [(size: String, icon: Image?)] = [] @AppStorage("settings.general.mini") private var mini: Bool = false @Environment(\.colorScheme) var colorScheme - + @Binding var showPopover: Bool + var body: some View { VStack(alignment: .center) { if !self.showDetails { @@ -161,18 +162,28 @@ struct FilesView: View { Task { updateOnMain { appState.appInfo = AppInfo.empty - appState.currentView = .empty + if mini { + appState.currentView = .apps + showPopover = false + } else { + appState.currentView = .empty + } } + let selectedItemsArray = Array(appState.selectedItems).filter { !$0.path.contains(".Trash") } killApp(appId: appState.appInfo.bundleIdentifier) { moveFilesToTrash(at: selectedItemsArray) { withAnimation { - appState.isReminderVisible.toggle() + updateOnMain { + appState.isReminderVisible.toggle() + } } refreshAppList(appState.appInfo) } } + } + } .disabled(appState.selectedItems.isEmpty) .buttonStyle(WindowActionButton(action: .accept)) @@ -230,10 +241,13 @@ struct FilesView: View { func refreshAppList(_ appInfo: AppInfo) { let sortedApps = getSortedApps() - appState.sortedApps.userApps = [] - appState.sortedApps.systemApps = [] - appState.sortedApps.userApps = sortedApps.userApps - appState.sortedApps.systemApps = sortedApps.systemApps + updateOnMain { + appState.sortedApps.userApps = [] + appState.sortedApps.systemApps = [] + appState.sortedApps.userApps = sortedApps.userApps + appState.sortedApps.systemApps = sortedApps.systemApps + } + } } diff --git a/Pearcleaner/Views/MiniMode.swift b/Pearcleaner/Views/MiniMode.swift index 0fb5e0a..e022c01 100644 --- a/Pearcleaner/Views/MiniMode.swift +++ b/Pearcleaner/Views/MiniMode.swift @@ -16,7 +16,7 @@ struct MiniMode: View { @State private var showUsr: Bool = true @State private var reload: Bool = false @AppStorage("settings.general.glass") private var glass: Bool = false - + @Binding var showPopover: Bool var body: some View { @@ -30,11 +30,11 @@ struct MiniMode: View { MiniEmptyView() } else if appState.currentView == .files { TopBarMini(reload: $reload, search: $search) - FilesView() + FilesView(showPopover: $showPopover) .id(appState.appInfo.id) } else if appState.currentView == .apps { TopBarMini(reload: $reload, search: $search) - MiniAppView(search: $search, reload: $reload) + MiniAppView(search: $search, reload: $reload, showPopover: $showPopover) } } // .padding(.leading, appState.sidebar ? 0 : 10) @@ -84,12 +84,14 @@ struct MiniEmptyView: View { if appState.isReminderVisible { Text("CMD + Z to undo") .font(.title2) - .foregroundStyle(Color("AccentColor").opacity(0.5)) + .foregroundStyle(Color("mode").opacity(0.5)) .fontWeight(.medium) .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + 2) { withAnimation { - appState.isReminderVisible = false + updateOnMain { + appState.isReminderVisible = false + } } } } @@ -115,6 +117,7 @@ struct MiniAppView: View { @State private var showUsr: Bool = true @Binding var reload: Bool @AppStorage("settings.general.mini") private var mini: Bool = false + @Binding var showPopover: Bool var body: some View { @@ -155,7 +158,7 @@ struct MiniAppView: View { VStack { Header(title: "User", count: filteredUserApps.count) ForEach(filteredUserApps, id: \.self) { appInfo in - AppListItems(appInfo: appInfo) + AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) if appInfo != filteredUserApps.last { Divider().padding(.horizontal, 5) } @@ -168,7 +171,7 @@ struct MiniAppView: View { VStack { Header(title: "System", count: filteredSystemApps.count) ForEach(filteredSystemApps, id: \.self) { appInfo in - AppListItems(appInfo: appInfo) + AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) if appInfo != filteredSystemApps.last { Divider().padding(.horizontal, 5) } @@ -190,9 +193,9 @@ struct MiniAppView: View { } } .transition(.move(edge: .leading)) - .popover(isPresented: $appState.showPopover, arrowEdge: .trailing) { + .popover(isPresented: $showPopover, arrowEdge: .trailing) { VStack { - FilesView() + FilesView(showPopover: $showPopover) .id(appState.appInfo.id) } .background( diff --git a/Pearcleaner/Views/TopBar.swift b/Pearcleaner/Views/TopBar.swift index ff46d72..1b7dd33 100644 --- a/Pearcleaner/Views/TopBar.swift +++ b/Pearcleaner/Views/TopBar.swift @@ -22,20 +22,10 @@ struct TopBar: View { Spacer() - if appState.currentView != .empty { - Button("") { - withAnimation(.easeInOut(duration: 0.5)) { - appState.currentView = .empty - appState.appInfo = AppInfo.empty - } - } - .buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode"))) - } - if appState.isReminderVisible { Text("CMD + Z to undo") .font(.title2) - .foregroundStyle(Color("AccentColor").opacity(0.5)) + .foregroundStyle(Color("mode").opacity(0.5)) .fontWeight(.medium) .onAppear { DispatchQueue.main.asyncAfter(deadline: .now() + 2) { @@ -48,6 +38,20 @@ struct TopBar: View { Spacer() + if appState.currentView != .empty { + Button("") { + withAnimation(.easeInOut(duration: 0.5)) { + appState.currentView = .empty + appState.appInfo = AppInfo.empty + } + } + .buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode"))) + } + + + +// Spacer() + // if sentinel { @@ -60,7 +64,7 @@ struct TopBar: View { } - .padding(.horizontal, 20) - .padding(.top, 20) + .padding(.horizontal, 5) + .padding(.top, 10) } } diff --git a/Pearcleaner/Views/TopBarMini.swift b/Pearcleaner/Views/TopBarMini.swift index 01701e0..0701a87 100644 --- a/Pearcleaner/Views/TopBarMini.swift +++ b/Pearcleaner/Views/TopBarMini.swift @@ -27,11 +27,11 @@ struct TopBarMini: View { SearchBarMini(search: $search, reload: $reload) // .frame(width: 150) - .offset(x: 20) - .padding(.horizontal, 30) +// .offset(x: 20) +// .padding(.horizontal, 30) // .padding(.top, 5) - Spacer() +// Spacer() ////////////// // Button("") { // withAnimation(.easeInOut(duration: 0.5)) { @@ -54,29 +54,38 @@ struct TopBarMini: View { if appState.currentView == .apps { Button("") { withAnimation(.easeInOut(duration: 0.5)) { - appState.currentView = .empty - appState.appInfo = AppInfo.empty + updateOnMain { + appState.currentView = .empty + appState.appInfo = AppInfo.empty + } } } .buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode"))) } else if appState.currentView == .empty { Button("") { withAnimation(.easeInOut(duration: 0.5)) { - appState.currentView = .apps + updateOnMain { + appState.currentView = .apps + } } } .buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps", color: Color("mode"))) } else if appState.currentView == .files { Button("") { withAnimation(.easeInOut(duration: 0.5)) { - appState.currentView = .empty - appState.appInfo = AppInfo.empty + updateOnMain { + appState.currentView = .empty + appState.appInfo = AppInfo.empty + } + } } .buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode"))) Button("") { withAnimation(.easeInOut(duration: 0.5)) { - appState.currentView = .apps + updateOnMain { + appState.currentView = .apps + } } } .buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps", color: Color("mode"))) @@ -84,9 +93,9 @@ struct TopBarMini: View { } - .padding(.horizontal, 10) - .padding(.top, 10) - .padding(.bottom, 15) + .padding(.horizontal, 5) + .padding(.top, 5) + .padding(.bottom, 10) } } @@ -98,8 +107,9 @@ struct SearchBarMini: View { var body: some View { HStack { TextField("Search", text: $search) - .textFieldStyle(SimpleSearchStyle(trash: true, reload: $reload, text: $search)) + .textFieldStyle(AnimatedSearchStyle(text: $search)) +// .textFieldStyle(SimpleSearchStyle(trash: true, reload: $reload, text: $search)) } - .frame(height: 20) +// .frame(height: 20) } }