diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index 00c905b..83b995b 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -543,7 +543,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 17; + CURRENT_PROJECT_VERSION = 18; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -561,7 +561,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.6; + MARKETING_VERSION = 2.7; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -578,7 +578,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 17; + CURRENT_PROJECT_VERSION = 18; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -596,7 +596,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.6; + MARKETING_VERSION = 2.7; 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 c3c15fd..6edb0e3 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -45,35 +45,35 @@ class AppState: ObservableObject } -class ProgressManager: ObservableObject { - @Published var progress: Double = 0.0 - @Published var total: Double = 0.0 - @Published var status: String = "Ready" - - func setTotal(_ total: Double) { - DispatchQueue.main.async { - self.total = total - } - } - - func updateProgress() { - DispatchQueue.main.async { - self.progress = min(max(0.0, self.progress + 1.0), Double(self.total)) - } - } - - func updateStatus(status: String) { - DispatchQueue.main.async { - self.status = status - } - } - - func resetProgress() { - DispatchQueue.main.async { - self.progress = 0.0 - } - } -} +//class ProgressManager: ObservableObject { +// @Published var progress: Double = 0.0 +// @Published var total: Double = 0.0 +// @Published var status: String = "Ready" +// +// func setTotal(_ total: Double) { +// DispatchQueue.main.async { +// self.total = total +// } +// } +// +// func updateProgress() { +// DispatchQueue.main.async { +// self.progress = min(max(0.0, self.progress + 1.0), Double(self.total)) +// } +// } +// +// func updateStatus(status: String) { +// DispatchQueue.main.async { +// self.status = status +// } +// } +// +// func resetProgress() { +// DispatchQueue.main.async { +// self.progress = 0.0 +// } +// } +//} diff --git a/Pearcleaner/Logic/Locations.swift b/Pearcleaner/Logic/Locations.swift index 0dd840b..e8e16a3 100644 --- a/Pearcleaner/Logic/Locations.swift +++ b/Pearcleaner/Logic/Locations.swift @@ -72,13 +72,10 @@ class Locations: ObservableObject { ]) // Append Application Support subfolders for deeper search - do { - let subfolders = try appSupSubfolders() - for folder in subfolders { - self.apps.paths.append("\(home)/Library/Application Support/\(folder)") - } - } catch { - print("Error getting subfolders: \(error)") + let subfolders = listAppSupportDirectories() + for folder in subfolders { + self.apps.paths.append("\(home)/Library/Application Support/\(folder)") +// writeLog(string: "Adding subfolder: \(home)/Library/Application Support/\(folder)") } // self.widgets = Category(name: "Widgets", paths: [ diff --git a/Pearcleaner/Logic/Logic.swift b/Pearcleaner/Logic/Logic.swift index 0916d91..c739ed3 100644 --- a/Pearcleaner/Logic/Logic.swift +++ b/Pearcleaner/Logic/Logic.swift @@ -269,24 +269,56 @@ func darwinCT() -> (String, String) { // Add subfolders of ~/Library/Application Support/ to locations for deeper search -func appSupSubfolders() throws -> [String] { - let fileManager = FileManager.default - let appSup = "\(home)/Library/Application Support/" - let subfolders = try fileManager.contentsOfDirectory(atPath: appSup) - let exclusionRegex = try NSRegularExpression(pattern: "\\bcom\\.apple\\b", options: []) +//func appSupSubfolders2() throws -> [String] { +// let fileManager = FileManager.default +// let appSup = "\(home)/Library/Application Support/" +// let subfolders = try fileManager.contentsOfDirectory(atPath: appSup) +// let exclusionRegex = try NSRegularExpression(pattern: "\\bcom\\.apple\\b", options: []) +// let exclusions = ["MobileSync", ".DS_Store", "Xcode", "SyncServices", "networkserviceproxy", "DiskImages", "CallHistoryTransactions", "App Store", "CloudDocs", "icdd", "iCloud", "Instruments", "AddressBook", "FaceTime", "AskPermission", "CallHistoryDB"] +// +// let allowedFolders = subfolders.filter { folder in +// let range = NSRange(location: 0, length: folder.utf16.count) +// return exclusionRegex.firstMatch(in: folder, options: [], range: range) == nil && !exclusions.contains(folder) +// } +// +// return allowedFolders +//} + + +func listAppSupportDirectories() -> [String] { + let home = FileManager.default.homeDirectoryForCurrentUser + let appSupportLocation = home.appendingPathComponent("Library/Application Support/") let exclusions = ["MobileSync", ".DS_Store", "Xcode", "SyncServices", "networkserviceproxy", "DiskImages", "CallHistoryTransactions", "App Store", "CloudDocs", "icdd", "iCloud", "Instruments", "AddressBook", "FaceTime", "AskPermission", "CallHistoryDB"] + let exclusionRegex = try! NSRegularExpression(pattern: "\\bcom\\.apple\\b", options: []) - let allowedFolders = subfolders.filter { folder in - let range = NSRange(location: 0, length: folder.utf16.count) - return exclusionRegex.firstMatch(in: folder, options: [], range: range) == nil && !exclusions.contains(folder) + do { + let fileManager = FileManager.default + let directoryContents = try fileManager.contentsOfDirectory(at: appSupportLocation, includingPropertiesForKeys: [.isDirectoryKey], options: .skipsHiddenFiles) + + let filteredDirectories: [String] = directoryContents.compactMap { url in + var isDirectory: ObjCBool = false + guard fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) else { + return nil + } + + let directoryName = url.lastPathComponent + + // Check for exclusions using regex and provided list + let excludeByRegex = exclusionRegex.firstMatch(in: directoryName, options: [], range: NSRange(location: 0, length: directoryName.utf16.count)) != nil + let excludeByList = exclusions.contains(directoryName) + + return isDirectory.boolValue && !excludeByRegex && !excludeByList ? directoryName : nil + } + + return filteredDirectories + } catch { + print("Error listing AppSupport directories: \(error.localizedDescription)") + return [] } - - return allowedFolders } - // Check if app is running before deleting app files func killApp(appId: String, completion: @escaping () -> Void = {}) { let runningApps = NSWorkspace.shared.runningApplications @@ -360,7 +392,7 @@ func findPathsForApp(appState: AppState, locations: Locations) { if collection.contains(itemURL) { - break + continue } // Catch web app plist files if appInfo.webApp { @@ -391,6 +423,7 @@ func findPathsForApp(appState: AppState, locations: Locations) { } } catch { +// writeLog(string: "Error processing location: \(location)\n\(error)") print("Error processing location:", location, error) continue } @@ -406,11 +439,16 @@ func findPathsForApp(appState: AppState, locations: Locations) { collection.append(contentsOf: groupContainers) let sortedCollection = collection.sorted(by: { $0.absoluteString < $1.absoluteString }) +// writeLog(string: "\n\nsortedCollection: \(sortedCollection)") + // Save to appState dispatchGroup.notify(queue: .main) { updateOnMain { appState.paths = sortedCollection appState.selectedItems = Set(sortedCollection) +// writeLog(string: "\n\nappStatePaths: \(appState.paths)") +// writeLog(string: "\n\nappStateSelected: \(appState.selectedItems)") + } } diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index 768b562..d93223a 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -30,7 +30,7 @@ struct SimpleButtonStyle: ButtonStyle { .frame(width: 20) .foregroundColor(hovered ? color : color.opacity(0.5)) } - .padding(8) + .padding(5) // .background { // if hovered && !(shield ?? false) { //// Circle() @@ -173,6 +173,7 @@ 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 @@ -195,25 +196,25 @@ struct SimpleSearchStyle: TextFieldStyle { .font(.title3) .textFieldStyle(PlainTextFieldStyle()) - Image(systemName: "arrow.triangle.2.circlepath") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 15, height: 15) - .padding(.trailing, 5) - .foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5)) - .onTapGesture { - withAnimation { - // Refresh Apps list - appState.reload.toggle() - let sortedApps = getSortedApps() - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - appState.sortedApps.userApps = sortedApps.userApps - appState.sortedApps.systemApps = sortedApps.systemApps - appState.reload.toggle() - } - } - - } +// Image(systemName: "arrow.triangle.2.circlepath") +// .resizable() +// .aspectRatio(contentMode: .fit) +// .frame(width: 15, height: 15) +// .padding(.trailing, 5) +// .foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5)) +// .onTapGesture { +// withAnimation { +// // Refresh Apps list +// appState.reload.toggle() +// let sortedApps = getSortedApps() +// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { +// appState.sortedApps.userApps = sortedApps.userApps +// appState.sortedApps.systemApps = sortedApps.systemApps +// appState.reload.toggle() +// } +// } +// +// } if trash && text != "" { Image(systemName: "xmark") @@ -240,10 +241,12 @@ struct SimpleSearchStyle: TextFieldStyle { } ) .onHover { hovering in - withAnimation(Animation.easeIn(duration: 0.15)) { + withAnimation(Animation.easeInOut(duration: 0.15)) { self.isHovered = hovering + self.isFocused = hovering } } + .focused($isFocused) } } diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index d194918..0507824 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -32,6 +32,13 @@ func resizeWindow(width: CGFloat, height: CGFloat) { } } +func resizeWindowAuto(windowSettings: WindowSettings) { + if let window = NSApplication.shared.windows.first { + let newSize = NSSize(width: windowSettings.loadWindowSettings().width, height: windowSettings.loadWindowSettings().height) + window.setContentSize(newSize) + } +} + // Check FDA func checkFullDiskAccessForApp() -> Bool { diff --git a/Pearcleaner/Logic/WindowSettings.swift b/Pearcleaner/Logic/WindowSettings.swift index 6e9a966..b2a13b5 100644 --- a/Pearcleaner/Logic/WindowSettings.swift +++ b/Pearcleaner/Logic/WindowSettings.swift @@ -10,22 +10,26 @@ import SwiftUI class WindowSettings { private let windowWidthKey = "windowWidthKey" private let windowHeightKey = "windowHeightKey" + private let windowWidthKeyMini = "windowWidthKeyMini" + private let windowHeightKeyMini = "windowHeightKeyMini" private let windowXKey = "windowXKey" private let windowYKey = "windowYKey" + @AppStorage("settings.general.mini") private var mini: Bool = false func saveWindowSettings(frame: NSRect) { - UserDefaults.standard.set(Float(frame.size.width), forKey: windowWidthKey) - UserDefaults.standard.set(Float(frame.size.height), forKey: windowHeightKey) + + UserDefaults.standard.set(Float(frame.size.width), forKey: mini ? windowWidthKeyMini : windowWidthKey) + UserDefaults.standard.set(Float(frame.size.height), forKey: mini ? windowHeightKeyMini : windowHeightKey) UserDefaults.standard.set(Float(frame.origin.x), forKey: windowXKey) UserDefaults.standard.set(Float(frame.origin.y), forKey: windowYKey) } func loadWindowSettings() -> NSRect { - let width = CGFloat(UserDefaults.standard.float(forKey: windowWidthKey)) - let height = CGFloat(UserDefaults.standard.float(forKey: windowHeightKey)) + + let width = CGFloat(UserDefaults.standard.float(forKey: mini ? windowWidthKeyMini : windowWidthKey)) + let height = CGFloat(UserDefaults.standard.float(forKey: mini ? windowHeightKeyMini : windowHeightKey)) let x = CGFloat(UserDefaults.standard.float(forKey: windowXKey)) let y = CGFloat(UserDefaults.standard.float(forKey: windowYKey)) - return NSRect(x: x, y: y, width: width, height: height) } } diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index 1115b57..481e3d8 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -20,6 +20,7 @@ struct PearcleanerApp: App { @AppStorage("settings.permissions.hasLaunched") private var hasLaunched: Bool = false @AppStorage("displayMode") var displayMode: DisplayMode = .system @AppStorage("settings.general.mini") private var mini: Bool = false + @AppStorage("settings.general.miniview") private var miniView: Bool = true @State private var search = "" @State private var showPopover: Bool = false @@ -65,6 +66,13 @@ struct PearcleanerApp: App { } } .onAppear { + + if miniView { + appState.currentView = .apps + } else { + appState.currentView = .empty + } + // Disable tabbing NSWindow.allowsAutomaticWindowTabbing = false @@ -79,6 +87,8 @@ struct PearcleanerApp: App { // Load progressbar total // appState.progressManager.total = Double(locations.apps.paths.count) + + Task { @@ -140,5 +150,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + +// func applicationDidFinishLaunching(_ notification: Notification, win: WindowSettings) { +// let frame = win.loadWindowSettings() +// NSApplication.shared.windows.first?.setFrame(frame, display: true) +// } + } diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index 223113b..1982062 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -10,10 +10,13 @@ import SwiftUI struct GeneralSettingsTab: View { @EnvironmentObject var appState: AppState + @State private var windowSettings = WindowSettings() @AppStorage("settings.general.glass") private var glass: Bool = true @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.dark") var isDark: Bool = true @AppStorage("settings.general.popover") private var popoverStay: Bool = true + @AppStorage("settings.general.miniview") private var miniView: Bool = true + @AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280 @AppStorage("displayMode") var displayMode: DisplayMode = .system @State private var selectedTheme = "Auto" private let themes = ["Auto", "Dark", "Light"] @@ -38,7 +41,26 @@ struct GeneralSettingsTab: View { RoundedRectangle(cornerRadius: 8) .fill(Color("mode").opacity(0.05)) ) - + + HStack { + VStack(alignment: .leading, spacing: 5) { + Text("Sidebar").font(.title2) + Text("Adjust sidebar width") + .font(.footnote) + .foregroundStyle(.gray) + } + Spacer() + Slider(value: $sidebarWidth, in: 200...400) { + Text("\(Int(sidebarWidth))") + } + .padding(.horizontal) + } + .padding() + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color("mode").opacity(0.05)) + ) + HStack { VStack(alignment: .leading, spacing: 5) { Text("Appearance").font(.title2) @@ -73,12 +95,7 @@ struct GeneralSettingsTab: View { break } } -// Toggle(isOn: $isDark, label: { -// }) -// .toggleStyle(.switch) -// .onChange(of: isDark) { newValue in -// displayMode.colorScheme = newValue ? .dark : .light -// } + } .padding() .background( @@ -99,10 +116,12 @@ struct GeneralSettingsTab: View { .toggleStyle(.switch) .onChange(of: mini) { newVal in if mini { - resizeWindow(width: 300, height: 300) - appState.currentView = .empty + resizeWindowAuto(windowSettings: windowSettings) +// resizeWindow(width: 300, height: 300) + appState.currentView = miniView ? .apps : .empty } else { - resizeWindow(width: 700, height: 500) + resizeWindowAuto(windowSettings: windowSettings) +// resizeWindow(width: 700, height: 500) if appState.appInfo.appName.isEmpty { appState.currentView = .empty } else { @@ -118,11 +137,31 @@ struct GeneralSettingsTab: View { ) + HStack { + VStack(alignment: .leading, spacing: 5) { + Text("Mini - Default View").font(.title2) + Text("Toggles drop target or apps list view on launch") + .font(.footnote) + .foregroundStyle(.gray) + } + Spacer() + Toggle(isOn: $miniView, label: { + }) + .toggleStyle(.switch) + .onChange(of: miniView) { newVal in + appState.currentView = newVal ? .apps : .empty + } + } + .padding() + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color("mode").opacity(0.05)) + ) HStack { VStack(alignment: .leading, spacing: 5) { - Text("Mini - Files View").font(.title2) - Text("Keeps file search view on top in mini mode") + Text("Mini - Popover").font(.title2) + Text("Keeps file search popover on top in mini mode") .font(.footnote) .foregroundStyle(.gray) } @@ -145,7 +184,7 @@ struct GeneralSettingsTab: View { } .padding(20) - .frame(width: 400, height: 350) + .frame(width: 400, height: 500) } diff --git a/Pearcleaner/Views/AppListItems.swift b/Pearcleaner/Views/AppListItems.swift index db0aaf4..71ced94 100644 --- a/Pearcleaner/Views/AppListItems.swift +++ b/Pearcleaner/Views/AppListItems.swift @@ -42,27 +42,36 @@ struct AppListItems: View { .truncationMode(.tail) .padding(.horizontal, 5) } + + + + Spacer() + if appInfo.webApp { Text("web") .font(.footnote) - .foregroundStyle(Color("mode").opacity(0.5)) + .foregroundStyle(Color("mode").opacity(0.3)) .frame(minWidth: 30, minHeight: 15) .padding(2) - .background(Color("mode").opacity(0.1)) - .clipShape(.capsule) + .background( + Capsule().strokeBorder(Color("mode").opacity(0.3), lineWidth: 1) + ) +// .background(Color("mode").opacity(0.1)) +// .clipShape(.capsule) } if appInfo.wrapped { Text("iOS") .font(.footnote) - .foregroundStyle(Color("mode").opacity(0.5)) + .foregroundStyle(Color("mode").opacity(0.3)) .frame(minWidth: 30, minHeight: 15) .padding(2) - .background(Color("mode").opacity(0.1)) - .clipShape(.capsule) + .background( + Capsule().strokeBorder(Color("mode").opacity(0.3), lineWidth: 1) + ) +// .background(Color("mode").opacity(0.1)) +// .clipShape(.capsule) } - - Spacer() Text(appInfo.appVersion) .font(.footnote) .foregroundStyle(Color("mode").opacity(0.5)) @@ -105,24 +114,24 @@ struct AppListItems: View { } } - .popover(isPresented: Binding( - get: { showPopover && appState.appInfo.id == appInfo.id}, - set: { _ in showPopover = false} - ), attachmentAnchor: .rect(.rect(CGRect(x: windowSettings.loadWindowSettings().width - 17, y: 15, width: 0, height: 0))), arrowEdge: .trailing) { +// .popover(isPresented: Binding( +// get: { showPopover && appState.appInfo.id == appInfo.id}, +// set: { _ in showPopover = false} +// ), attachmentAnchor: .rect(.rect(CGRect(x: windowSettings.loadWindowSettings().width - 17, y: 15, width: 0, height: 0))), arrowEdge: .trailing) { // .popover(isPresented: $showPopover, arrowEdge: .trailing) { - VStack { - FilesView(showPopover: $showPopover, search: $search) - .id(appState.appInfo.id) - } - .interactiveDismissDisabled(popoverStay) - .background( - Rectangle() - .fill(Color("pop")) - .padding(-80) - ) - .frame(minWidth: 650, minHeight: 500) - - } +// VStack { +// FilesView(showPopover: $showPopover, search: $search) +// .id(appState.appInfo.id) +// } +// .interactiveDismissDisabled(popoverStay) +// .background( +// Rectangle() +// .fill(Color("pop")) +// .padding(-80) +// ) +// .frame(minWidth: 650, minHeight: 500) +// +// } } } diff --git a/Pearcleaner/Views/AppListView.swift b/Pearcleaner/Views/AppListView.swift index 9d39377..c6781ba 100644 --- a/Pearcleaner/Views/AppListView.swift +++ b/Pearcleaner/Views/AppListView.swift @@ -11,6 +11,7 @@ import SwiftUI struct AppListView: View { @EnvironmentObject var appState: AppState @AppStorage("settings.general.glass") private var glass: Bool = false + @AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280 @Binding var search: String @State private var showSys: Bool = true @State private var showUsr: Bool = true @@ -47,14 +48,15 @@ struct AppListView: View { ProgressView("Refreshing applications") Spacer() } - .frame(width: 250) + .frame(width: sidebarWidth) .padding(.vertical) } else { VStack(alignment: .center) { VStack(alignment: .center, spacing: 20) { HStack { - SearchBar(search: $search) + SearchBarMiniBottom(search: $search) +// SearchBar(search: $search) // Button("") { // withAnimation(.easeInOut(duration: 0.5)) { @@ -72,14 +74,14 @@ struct AppListView: View { // .buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh app list", color: Color("mode"))) } } - .padding(.horizontal) +// .padding(.horizontal) .padding(.top, 20) .padding(.bottom) ScrollView { - VStack(alignment: .leading) { - + LazyVStack(alignment: .leading, pinnedViews: [.sectionHeaders]) { + if filteredUserApps.count > 0 { VStack { Header(title: "User", count: filteredUserApps.count) @@ -91,7 +93,7 @@ struct AppListView: View { } // .padding(.bottom) } - + } if filteredSystemApps.count > 0 { @@ -113,7 +115,7 @@ struct AppListView: View { .scrollIndicators(.never) } - .frame(width: 250) + .frame(width: sidebarWidth) .padding(.vertical) } @@ -130,7 +132,7 @@ struct AppListView: View { // Details View VStack(spacing: 0) { - if appState.currentView == .empty { + if appState.currentView == .empty || appState.currentView == .apps { TopBar() AppDetailsEmptyView(showPopover: $showPopover) } else if appState.currentView == .files { @@ -236,18 +238,55 @@ struct SearchBar: View { struct Header: View { let title: String let count: Int - + @State private var hovered = false + @EnvironmentObject var appState: AppState + + var body: some View { HStack { Text(title).opacity(0.5) + +// Spacer() + + HStack { + if hovered { + withAnimation() { + Image(systemName: "arrow.circlepath") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 19, height: 19) + .onTapGesture { + withAnimation { + // Refresh Apps list + appState.reload.toggle() + let sortedApps = getSortedApps() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + appState.sortedApps.userApps = sortedApps.userApps + appState.sortedApps.systemApps = sortedApps.systemApps + appState.reload.toggle() + } + } + } + .help("Refresh apps") + } + } else { + Text("\(count)") + .font(.system(size: 10)) + .frame(minWidth: count > 99 ? 30 : 20, minHeight: 15) + .padding(2) + .background(Color("mode").opacity(0.1)) + .clipShape(.capsule) + // .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous)) + } + } + .onHover { hovering in + withAnimation() { + hovered = hovering + } + } + Spacer() - Text("\(count)") - .font(.system(size: 10)) - .frame(minWidth: count > 99 ? 30 : 20, minHeight: 15) - .padding(2) - .background(Color("mode").opacity(0.1)) - .clipShape(.capsule) - // .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous)) + } .padding(4) } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index 7f24723..7c9cddf 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -34,20 +34,7 @@ struct FilesView: View { ProgressView() .progressViewStyle(.linear) .frame(width: 400, height: 10) -// ProgressView("\(appState.progressManager.status)", value: appState.progressManager.progress, total: Double(appState.progressManager.total)) -// .progressViewStyle(LinearProgressViewStyle(tint: .blue)) -// .frame(width: 400, height: 10) -// .padding(.top) -// .onReceive(appState.progressManager.$progress) { newProgress in -// DispatchQueue.main.async { -// appState.objectWillChange.send() -// } -// } -// .onReceive(appState.progressManager.$status) { newStatus in -// DispatchQueue.main.async { -// appState.objectWillChange.send() -// } -// } + Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -272,10 +259,10 @@ struct FilesView: View { } if let appSize = totalSizeOnDisk(for: appState.paths) { self.appSize = "\(appSize)" + self.showDetails = true } else { print("Error calculating the total size on disk.") } - self.showDetails = true } } else { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { diff --git a/Pearcleaner/Views/MiniMode.swift b/Pearcleaner/Views/MiniMode.swift index 3860841..5b05fea 100644 --- a/Pearcleaner/Views/MiniMode.swift +++ b/Pearcleaner/Views/MiniMode.swift @@ -39,24 +39,24 @@ struct MiniMode: View { } // .padding(.leading, appState.sidebar ? 0 : 10) .transition(.move(edge: .leading)) -// .popover(isPresented: $showPopover, arrowEdge: .trailing) { -// VStack { -// FilesView(showPopover: $showPopover, search: $search) -// .id(appState.appInfo.id) -// } -// .interactiveDismissDisabled(popoverStay) -// .background( -// Rectangle() -// .fill(Color("pop")) -// .padding(-80) -// ) -// .frame(minWidth: 600, minHeight: 500) -// -// } + .popover(isPresented: $showPopover, arrowEdge: .trailing) { + VStack { + FilesView(showPopover: $showPopover, search: $search) + .id(appState.appInfo.id) + } + .interactiveDismissDisabled(popoverStay) + .background( + Rectangle() + .fill(Color("pop")) + .padding(-80) + ) + .frame(minWidth: 650, minHeight: 500) + + } } - .frame(minWidth: 300, minHeight: 300) + .frame(minWidth: 300, minHeight: 335) .edgesIgnoringSafeArea(.all) .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) @@ -184,12 +184,37 @@ struct MiniAppView: View { .padding(.vertical) } else { VStack(alignment: .center) { - + +// if appState.currentView == .apps { +// HStack(alignment: .center, spacing: 0) { +// Spacer() +// +// Button("") { +// withAnimation(.easeInOut(duration: 0.5)) { +// // updateOnMain { +// appState.currentView = .empty +// appState.appInfo = AppInfo.empty +// showPopover = false +// // } +// } +// } +// .buttonStyle(SimpleButtonStyle(icon: "arrow.backward.square", help: "Back to Drop Zone", color: Color("mode"))) +// .padding(.leading, 10) +// .padding(.trailing, 0) +// +// SearchBarMiniBottom(search: $search) +// +// Spacer() +// } +// .padding(.top, 30) +// } + ScrollView { - VStack(alignment: .leading) { - + LazyVStack(alignment: .leading, pinnedViews: [.sectionHeaders]) { + if filteredUserApps.count > 0 { + VStack { Header(title: "User", count: filteredUserApps.count) ForEach(filteredUserApps, id: \.self) { appInfo in @@ -203,6 +228,7 @@ struct MiniAppView: View { } if filteredSystemApps.count > 0 { + VStack { Header(title: "System", count: filteredSystemApps.count) ForEach(filteredSystemApps, id: \.self) { appInfo in @@ -219,7 +245,10 @@ struct MiniAppView: View { } .scrollIndicators(.never) - + + if appState.currentView == .apps { + SearchBarMiniBottom(search: $search) + } } .padding(.bottom) } diff --git a/Pearcleaner/Views/TopBar.swift b/Pearcleaner/Views/TopBar.swift index a160d28..dd7b461 100644 --- a/Pearcleaner/Views/TopBar.swift +++ b/Pearcleaner/Views/TopBar.swift @@ -37,7 +37,7 @@ struct TopBar: View { Spacer() - if appState.currentView != .empty { + if appState.currentView != .empty || appState.currentView != .apps { Button("") { withAnimation(.easeInOut(duration: 0.5)) { appState.currentView = .empty diff --git a/Pearcleaner/Views/TopBarMini.swift b/Pearcleaner/Views/TopBarMini.swift index 71c3a00..d738e7a 100644 --- a/Pearcleaner/Views/TopBarMini.swift +++ b/Pearcleaner/Views/TopBarMini.swift @@ -15,88 +15,99 @@ struct TopBarMini: View { @Binding var search: String @Binding var showPopover: Bool @EnvironmentObject var appState: AppState - + var body: some View { HStack(alignment: .center, spacing: 5) { - + Spacer() - - if appState.currentView == .apps { -// HStack { -// Spacer() - - SearchBarMini(search: $search) -// .frame(width: 150) -// .offset(x: 20) -// .padding(.horizontal, 30) -// .padding(.top, 5) - -// Spacer() ////////////// - -// 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() -// } -// -// } + +// if appState.currentView == .apps { +// Button("") { +// withAnimation(.easeInOut(duration: 0.5)) { +// // updateOnMain { +// appState.currentView = .empty +// appState.appInfo = AppInfo.empty +// showPopover = false +// // } // } -// .buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh app list", color: Color("mode"))) -// .padding(.leading, 5) // } - } - - if appState.currentView == .apps { +// .buttonStyle(SimpleButtonStyle(icon: "arrow.backward.square", help: "Back to Drop Zone", color: Color("mode"))) +// } else + if appState.currentView == .empty { Button("") { withAnimation(.easeInOut(duration: 0.5)) { -// updateOnMain { - appState.currentView = .empty - appState.appInfo = AppInfo.empty - showPopover = false -// } + // updateOnMain { + appState.currentView = .apps + // } } } - .buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Back to Drop Zone", color: Color("mode"))) - } else if appState.currentView == .empty { - Button("") { - withAnimation(.easeInOut(duration: 0.5)) { -// updateOnMain { - appState.currentView = .apps -// } - } - } - .buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps List", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "list.dash", help: "Apps List", color: Color("mode"))) } else if appState.currentView == .files { Button("") { withAnimation(.easeInOut(duration: 0.5)) { -// updateOnMain { - appState.currentView = .empty - appState.appInfo = AppInfo.empty -// } - + // updateOnMain { + appState.currentView = .empty + appState.appInfo = AppInfo.empty + // } + } } - .buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Back to Drop Zone", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", help: "Drop Target", color: Color("mode"))) Button("") { withAnimation(.easeInOut(duration: 0.5)) { -// updateOnMain { - appState.currentView = .apps -// } + // updateOnMain { + appState.currentView = .apps + // } } } - .buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps List", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "list.dash", help: "Apps List", color: Color("mode"))) } - - + + if appState.currentView == .apps { + HStack { + Spacer() + +// SearchBarMini(search: $search) +// .frame(width: 150) +// .offset(x: 20) +// .padding(.horizontal, 30) +// .padding(.top, 5) + + Button("") { + withAnimation(.easeInOut(duration: 0.5)) { + // updateOnMain { + appState.currentView = .empty + appState.appInfo = AppInfo.empty + showPopover = false + // } + } + } + .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", help: "Drop Target", color: Color("mode"))) + +// Spacer() + +// Button("") { +// withAnimation { +// // Refresh Apps list +// appState.reload.toggle() +// let sortedApps = getSortedApps() +// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { +// appState.sortedApps.userApps = sortedApps.userApps +// appState.sortedApps.systemApps = sortedApps.systemApps +// appState.reload.toggle() +// } +// } +// } +// .buttonStyle(SimpleButtonStyle(icon: "arrow.circlepath", help: "Refresh app list", color: Color("mode"))) +// .padding(.leading, 5) + } + } + + } - .padding(.horizontal, 5) - .padding(.top, 5) - .padding(.bottom, 10) + .padding(.horizontal, 10) + .padding(.top, 10) + .padding(.bottom, 5) } } @@ -108,8 +119,21 @@ struct SearchBarMini: View { HStack { TextField("Search", text: $search) .textFieldStyle(AnimatedSearchStyle(text: $search)) -// .textFieldStyle(SimpleSearchStyle(trash: true, reload: $reload, text: $search)) + // .textFieldStyle(SimpleSearchStyle(trash: true, reload: $reload, text: $search)) } -// .frame(height: 20) + .frame(height: 20) + } +} + +struct SearchBarMiniBottom: View { + @Binding var search: String + + var body: some View { + HStack { + TextField("Search", text: $search) + .textFieldStyle(SimpleSearchStyle(trash: true, text: $search)) + } + .padding(.horizontal) + .padding(.bottom, 0) } }