diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index 52eadf5..1f5edf4 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -19,7 +19,6 @@ C7575D392B0182DE006A600A /* PearcleanerSentinel in CopyFiles */ = {isa = PBXBuildFile; fileRef = C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; }; C76D084A2AF83C6E00D07867 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08492AF83C6E00D07867 /* General.swift */; }; - C76D084C2AF83C9D00D07867 /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D084B2AF83C9D00D07867 /* Permissions.swift */; }; C76D08512AF850F300D07867 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08502AF850F300D07867 /* About.swift */; }; C76D08552AF89CDE00D07867 /* AppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08542AF89CDE00D07867 /* AppListView.swift */; }; C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */; }; @@ -32,13 +31,11 @@ C77B90232AF2D616009CC655 /* FilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90222AF2D616009CC655 /* FilesView.swift */; }; C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */; }; 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 */; }; - C7D31D462AFDA90C00C7ED9E /* Sentinel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D452AFDA90C00C7ED9E /* Sentinel.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 */; }; - C7D31D552B008CB900C7ED9E /* Shapes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D542B008CB900C7ED9E /* Shapes.swift */; }; - C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D582B00A49300C7ED9E /* DropTarget.swift */; }; C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; }; /* End PBXBuildFile section */ @@ -76,7 +73,6 @@ C72893112AFD51EA00C8C1CD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; C76D08472AF83C3F00D07867 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = ""; }; C76D08492AF83C6E00D07867 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = ""; }; - C76D084B2AF83C9D00D07867 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = ""; }; C76D08502AF850F300D07867 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = ""; }; C76D08542AF89CDE00D07867 /* AppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListView.swift; sourceTree = ""; }; C77B90002AF18E2E009CC655 /* Pearcleaner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pearcleaner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -93,13 +89,11 @@ C7A27E7F2AFD70DD00166168 /* PearcleanerSentinel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PearcleanerSentinel.entitlements; sourceTree = ""; }; C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.alienator88.PearcleanerSentinel.plist; sourceTree = ""; }; 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 = ""; }; - C7D31D452AFDA90C00C7ED9E /* Sentinel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sentinel.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 = ""; }; - C7D31D542B008CB900C7ED9E /* Shapes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shapes.swift; sourceTree = ""; }; - C7D31D582B00A49300C7ED9E /* DropTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropTarget.swift; sourceTree = ""; }; C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -142,9 +136,9 @@ C72893042AFD42E600C8C1CD /* DeepLink.swift */, C77B901B2AF193A3009CC655 /* Styles.swift */, C7D31D502AFF00F300C7ED9E /* Locations.swift */, - C7D31D542B008CB900C7ED9E /* Shapes.swift */, C7CF47232B3B3F1700979C5F /* WindowSettings.swift */, C7A9CE462B89164700EB6E78 /* Authorization.swift */, + C7ACADE52B92A737000B5845 /* Features.swift */, ); path = Logic; sourceTree = ""; @@ -171,8 +165,6 @@ children = ( C77B901D2AF1A9DB009CC655 /* SettingsWindow.swift */, C76D08492AF83C6E00D07867 /* General.swift */, - C76D084B2AF83C9D00D07867 /* Permissions.swift */, - C7D31D452AFDA90C00C7ED9E /* Sentinel.swift */, C76D08472AF83C3F00D07867 /* Update.swift */, C76D08502AF850F300D07867 /* About.swift */, ); @@ -230,7 +222,6 @@ C7D31D472AFEB23700C7ED9E /* TopBar.swift */, C7045A292B03FAF000376976 /* TopBarMini.swift */, C7D31D492AFEB26700C7ED9E /* AppListItems.swift */, - C7D31D582B00A49300C7ED9E /* DropTarget.swift */, C7045A272B03E71D00376976 /* MiniMode.swift */, C71848432B8D2D600046CB13 /* ZombieView.swift */, ); @@ -358,23 +349,20 @@ C7045A322B068AD700376976 /* NewWin.swift in Sources */, C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */, C77B901A2AF1938F009CC655 /* Logic.swift in Sources */, - C7D31D462AFDA90C00C7ED9E /* Sentinel.swift in Sources */, - C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */, C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */, C77B901C2AF193A3009CC655 /* Styles.swift in Sources */, C7045A282B03E71D00376976 /* MiniMode.swift in Sources */, C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */, C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */, - C76D084C2AF83C9D00D07867 /* Permissions.swift in Sources */, C76D084A2AF83C6E00D07867 /* General.swift in Sources */, C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */, - C7D31D552B008CB900C7ED9E /* Shapes.swift in Sources */, C7045A362B068DD700376976 /* PermView.swift in Sources */, C76D08512AF850F300D07867 /* About.swift in Sources */, C77B90182AF19382009CC655 /* AppCommands.swift in Sources */, C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */, C77B901E2AF1A9DB009CC655 /* SettingsWindow.swift in Sources */, C7D31D482AFEB23700C7ED9E /* TopBar.swift in Sources */, + C7ACADE62B92A737000B5845 /* Features.swift in Sources */, C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */, C76D08552AF89CDE00D07867 /* AppListView.swift in Sources */, C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */, @@ -551,7 +539,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 18; + CURRENT_PROJECT_VERSION = 19; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -569,7 +557,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.7; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -586,7 +574,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 18; + CURRENT_PROJECT_VERSION = 19; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -604,7 +592,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 2.7; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Pearcleaner/Logic/AppCommands.swift b/Pearcleaner/Logic/AppCommands.swift index 106bfac..2e30ee3 100644 --- a/Pearcleaner/Logic/AppCommands.swift +++ b/Pearcleaner/Logic/AppCommands.swift @@ -46,12 +46,6 @@ struct AppCommands: Commands { updateOnMain { 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() -// } } } label: { Text("Refresh Apps") @@ -86,6 +80,8 @@ struct AppCommands: Commands { Label("Undo Removal", systemImage: "clear") } .keyboardShortcut("z", modifiers: .command) + + } diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index 1ffe08c..e311ca4 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -48,7 +48,6 @@ class AppState: ObservableObject self.zombieFile = ZombieFile( id: UUID(), -// files: [], fileSize: [:], fileIcon: [:] ) @@ -86,7 +85,6 @@ struct AppInfo: Identifiable, Equatable, Hashable { struct ZombieFile: Identifiable, Equatable, Hashable { let id: UUID -// var files: [URL] var fileSize: [URL:Int64] var fileIcon: [URL:NSImage?] var totalSize: Int64 @@ -184,78 +182,4 @@ enum DisplayMode: Int, CaseIterable { } -//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 -// } -// } -//} - -//let (cacheDir, tempDir) = darwinCT() -//let locations: [String] = [ -// "\(home)/Library", -// "\(home)/Library/Application Scripts", -// "\(home)/Library/Application Support", -// "\(home)/Library/Application Support/CrashReporter", -// "\(home)/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments", -// "\(home)/Library/Containers", -// "\(home)/Library/Group Containers", -// "\(home)/Library/Caches", -// "\(home)/Library/HTTPStorages", -// "\(home)/Library/Group Containers", -// "\(home)/Library/Internet Plug-Ins", -// "\(home)/Library/LaunchAgents", -// "\(home)/Library/Logs", -// "\(home)/Library/Preferences", -// "\(home)/Library/Preferences/ByHost", -// "\(home)/Library/Saved Application State", -// "\(home)/Library/WebKit", -// "/Users/Shared", -// "/Users/Library", -// "/Library", -// "/Library/Application Support", -// "/Library/Application Support/CrashReporter", -// "/Library/Caches", -// "/Library/Extensions", -// "/Library/Internet Plug-Ins", -// "/Library/LaunchAgents", -// "/Library/LaunchDaemons", -// "/Library/Logs", -// "/Library/Logs/DiagnosticReports", -// "/Library/Preferences", -// "/Library/PrivilegedHelperTools", -// "/private/var/db/receipts", -// "/private/tmp", -// "/usr/local/bin", -// "/usr/local/etc", -// "/usr/local/opt", -// "/usr/local/sbin", -// "/usr/local/share", -// "/usr/local/var", -// cacheDir, -// tempDir -//] diff --git a/Pearcleaner/Logic/DeepLink.swift b/Pearcleaner/Logic/DeepLink.swift index b78024d..39f8026 100644 --- a/Pearcleaner/Logic/DeepLink.swift +++ b/Pearcleaner/Logic/DeepLink.swift @@ -35,36 +35,6 @@ class DeeplinkManager { let pathURL = URL(fileURLWithPath: path) let appInfo = getAppInfo(atPath: pathURL) showAppInFiles(appInfo: appInfo!, mini: mini, appState: appState, locations: locations, showPopover: $showPopover) - -// showPopover = false -// updateOnMain { -// appState.appInfo = .empty -// if let storedAppInfo = appState.appInfoStore.first(where: { $0.path == appInfo?.path }) { -// appState.appInfo = storedAppInfo -//// appState.paths = storedAppInfo.fileSize.keys.map { $0 }//storedAppInfo.files -// appState.selectedItems = Set(storedAppInfo.files) -// withAnimation(Animation.easeIn(duration: 0.4)) { -// if self.mini { -// self.showPopover.toggle() -// } else { -// appState.currentView = .files -// } -// } -// } else { -// // Handle the case where the appInfo is not found in the store -// printOS("AppInfo not found in the cached store, searching again") -// appState.appInfo = .empty -// appState.appInfo = appInfo! -// findPathsForApp(appInfo: appInfo!, appState: appState, locations: locations) -// withAnimation(Animation.easeIn(duration: 0.4)) { -// if self.mini { -// self.showPopover.toggle() -// } else { -// appState.currentView = .files -// } -// } -// } -// } } else { printOS("No path query parameter found in the URL") } @@ -77,45 +47,6 @@ class DeeplinkManager { func handleAppBundle(url: URL, appState: AppState, locations: Locations) { let appInfo = getAppInfo(atPath: url) showAppInFiles(appInfo: appInfo!, mini: mini, appState: appState, locations: locations, showPopover: $showPopover) - -// showPopover = false -// updateOnMain { -// appState.appInfo = .empty -// if let storedAppInfo = appState.appInfoStore.first(where: { $0.path == appInfo?.path }) { -// appState.appInfo = storedAppInfo -//// appState.paths = storedAppInfo.fileSize.keys.map { $0 }//storedAppInfo.files -// appState.selectedItems = Set(storedAppInfo.files) -// withAnimation(Animation.easeIn(duration: 0.4)) { -// if self.mini { -// self.showPopover.toggle() -// } else { -// appState.currentView = .files -// } -// } -// } else { -// // Handle the case where the appInfo is not found in the store -// printOS("AppInfo not found in the cached store, searching again") -// appState.appInfo = .empty -// appState.appInfo = appInfo! -// findPathsForApp(appInfo: appInfo!, appState: appState, locations: locations) -// withAnimation(Animation.easeIn(duration: 0.4)) { -// if self.mini { -// self.showPopover.toggle() -// } else { -// appState.currentView = .files -// } -// } -// } -// } -// updateOnMain { -// appState.appInfo = appInfo! -// findPathsForApp(appState: appState, locations: locations) -// if self.mini { -// self.showPopover = true -// } else { -// appState.currentView = .files -// } -// } } } diff --git a/Pearcleaner/Logic/Features.swift b/Pearcleaner/Logic/Features.swift new file mode 100644 index 0000000..4d8e46e --- /dev/null +++ b/Pearcleaner/Logic/Features.swift @@ -0,0 +1,49 @@ +// +// Features.swift +// Pearcleaner +// +// Created by Alin Lupascu on 3/1/24. +// + +import Foundation +import SwiftUI + +func getFeatures(appState: AppState, show: Binding, features: Binding) { + + let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/contents/features.json")! + var request = URLRequest(url: url) + request.setValue("application/vnd.github.VERSION.raw", forHTTPHeaderField: "Accept") + request.setValue("2022-11-28", forHTTPHeaderField: "X-GitHub-Api-Version") + + URLSession.shared.dataTask(with: request) { data, response, error in + if let data = data { + do { + let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) + if let jsonDict = jsonObject as? [String: String], + let bundleVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, + let featureText = jsonDict[bundleVersion] { + if features.wrappedValue != featureText.featureFormat() { + updateOnMain { + features.wrappedValue = featureText.featureFormat() + show.wrappedValue = true + } + } else { + show.wrappedValue = false +// print("Same version") + } + + } else { + show.wrappedValue = false +// print("No features for version found") + } + } catch { + show.wrappedValue = false + printOS("Error reading features JSON from GitHub: \(error.localizedDescription)") + } + } else { + show.wrappedValue = false + printOS("Error reading features JSON from GitHub: \(error?.localizedDescription ?? "Unknown error")") + } + }.resume() +} + diff --git a/Pearcleaner/Logic/Logic.swift b/Pearcleaner/Logic/Logic.swift index 32e3427..2e59b8c 100644 --- a/Pearcleaner/Logic/Logic.swift +++ b/Pearcleaner/Logic/Logic.swift @@ -7,8 +7,6 @@ import Foundation import SwiftUI -//import AudioToolbox - // Get list of apps and sort it func getSortedApps() -> (systemApps: [AppInfo], userApps: [AppInfo]) { @@ -417,7 +415,7 @@ func findPathsForApp(appInfo: AppInfo = .empty, appState: AppState, locations: L // Append group containers let groupContainers = getGroupContainers(bundleURL: appState.appInfo.path) collection.append(contentsOf: groupContainers) - let sortedCollection = collection.sorted(by: { $0.absoluteString < $1.absoluteString }) + var sortedCollection = collection.sorted(by: { $0.absoluteString < $1.absoluteString }) // Calculate file details (sizes and icons) @@ -435,6 +433,11 @@ func findPathsForApp(appInfo: AppInfo = .empty, appState: AppState, locations: L fileIcon[path] = icon } + if sortedCollection.count == 1 { + if let firstURL = sortedCollection.first, firstURL.path.contains(".Trash") { + sortedCollection = [] + } + } // Save to appState dispatchGroup.notify(queue: .main) { @@ -457,39 +460,61 @@ func findPathsForApp(appInfo: AppInfo = .empty, appState: AppState, locations: L // Load app paths on launch -func loadAllPaths(allApps: [AppInfo], appState: AppState, locations: Locations) { +func loadAllPaths(allApps: [AppInfo], appState: AppState, locations: Locations, reverseAddon: Bool = false, completion: @escaping () -> Void = {}) { + + let dispatchGroup = DispatchGroup() + let retryLimit = 120 + var currentRetryCount = 0 + + func checkCompletion() { + if appState.appInfoStore.count == allApps.count { + if reverseAddon { + reversePathsSearch(appState: appState, locations: locations) + } + completion() + } else { + if currentRetryCount < retryLimit { + currentRetryCount += 1 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + checkCompletion() + } + } else { + printOS("loadAllPaths - Retry limit timed out. Unable to load all paths within 1 minute.") + } + } + } + + dispatchGroup.enter() + updateOnMain { appState.appInfoStore.removeAll() } - for app in allApps { - findPathsForApp(appInfo: app, appState: appState, locations: locations, backgroundRun: true) { - } + + dispatchGroup.notify(queue: .main) { + checkCompletion() } + + DispatchQueue.global(qos: .background).async { + for app in allApps { + findPathsForApp(appInfo: app, appState: appState, locations: locations, backgroundRun: true) + } + dispatchGroup.leave() + + } + } // Load item in Files view func showAppInFiles(appInfo: AppInfo, mini: Bool, appState: AppState, locations: Locations, showPopover: Binding) { showPopover.wrappedValue = false - DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { updateOnMain { appState.appInfo = .empty + appState.selectedItems = [] if let storedAppInfo = appState.appInfoStore.first(where: { $0.path == appInfo.path }) { appState.appInfo = storedAppInfo appState.selectedItems = Set(storedAppInfo.files) - withAnimation(Animation.easeIn(duration: 0.3)) { - if mini { - appState.currentView = .files - showPopover.wrappedValue.toggle() - } else { - appState.currentView = .files - } - } - } else { - updateOnMain { - appState.showProgress = true - } - // Handle the case where the appInfo is not found in the store withAnimation(Animation.easeIn(duration: 0.4)) { if mini { appState.currentView = .files @@ -498,17 +523,25 @@ func showAppInFiles(appInfo: AppInfo, mini: Bool, appState: AppState, locations: appState.currentView = .files } } - printOS("AppInfo not found in the cached store, searching again") - appState.appInfo = .empty + } else { + // Handle the case where the appInfo is not found in the store + withAnimation(Animation.easeIn(duration: 0.4)) { + appState.showProgress = true + if mini { + appState.currentView = .files + showPopover.wrappedValue.toggle() + } else { + appState.currentView = .files + } + } appState.appInfo = appInfo findPathsForApp(appInfo: appInfo, appState: appState, locations: locations) { - updateOnMain { - appState.selectedItems = Set(appInfo.files) - appState.showProgress = false - + withAnimation(Animation.easeIn(duration: 0.4)) { + updateOnMain { + appState.showProgress = false + } } } - } } @@ -555,81 +588,80 @@ func getGroupContainers(bundleURL: URL) -> [URL] { func reversePathsSearch(appState: AppState, locations: Locations, completion: @escaping () -> Void = {}) { Task(priority: .high) { - var collection: [URL] = [] - let fileManager = FileManager.default - let dispatchGroup = DispatchGroup() - let allPaths = appState.appInfoStore.flatMap { $0.files.map { $0.path.pearFormat() } } - let allNames = appState.appInfoStore.map { $0.appName.pearFormat() } - let skipped = ["apple", "comapple", "temporary", "btserver", "proapps", "scripteditor", "ilife", "livefsd", "siritoday", "addressbook", "animoji", "appstore", "askpermission", "callhistory", "clouddocs", "diskimages", "dock", "facetime", "fileprovider", "instruments", "knowledge", "mobilesync", "syncservices", "homeenergyd", "icloud", "icdd", "networkserviceproxy", "familycircle", "geoservices", "installation", "passkit", "sharedimagecache", "desktop", "mbuseragent", "swiftpm", "baseband", "coresimulator", "photoslegacyupgrade", "photosupgrade", "siritts", "ipod", "globalpreferences", "apmanalytics", "apmexperiment", "avatarcache", "byhost", "contextstoreagent", "mobilemeaccounts", "intentbuilderc", "loginwindow", "momc", "replayd", "sharedfilelistd", "clang", "audiocomponent", "csexattrcryptoservice", "livetranscriptionagent", "sandobxhelper", "statuskitagent", "betaenrollmentd", "contentlinkingd", "diagnosticextensionsd", "gamed", "heard", "homed", "itunescloudd", "lldb", "mds", "mediaanalysisd", "metrickitd", "mobiletimerd", "proactived", "ptpcamerad", "studentd", "talagent", "watchlistd", "apptranslocation", "xcrun", "ds_store", "caches", "crashreporter"] // Skip system folders + var collection: [URL] = [] + let fileManager = FileManager.default + let dispatchGroup = DispatchGroup() + let allPaths = appState.appInfoStore.flatMap { $0.files.map { $0.path.pearFormat() } } + let allNames = appState.appInfoStore.map { $0.appName.pearFormat() } + let skipped = ["apple", "comapple", "temporary", "btserver", "proapps", "scripteditor", "ilife", "livefsd", "siritoday", "addressbook", "animoji", "appstore", "askpermission", "callhistory", "clouddocs", "diskimages", "dock", "facetime", "fileprovider", "instruments", "knowledge", "mobilesync", "syncservices", "homeenergyd", "icloud", "icdd", "networkserviceproxy", "familycircle", "geoservices", "installation", "passkit", "sharedimagecache", "desktop", "mbuseragent", "swiftpm", "baseband", "coresimulator", "photoslegacyupgrade", "photosupgrade", "siritts", "ipod", "globalpreferences", "apmanalytics", "apmexperiment", "avatarcache", "byhost", "contextstoreagent", "mobilemeaccounts", "intentbuilderc", "loginwindow", "momc", "replayd", "sharedfilelistd", "clang", "audiocomponent", "csexattrcryptoservice", "livetranscriptionagent", "sandobxhelper", "statuskitagent", "betaenrollmentd", "contentlinkingd", "diagnosticextensionsd", "gamed", "heard", "homed", "itunescloudd", "lldb", "mds", "mediaanalysisd", "metrickitd", "mobiletimerd", "proactived", "ptpcamerad", "studentd", "talagent", "watchlistd", "apptranslocation", "xcrun", "ds_store", "caches", "crashreporter"] // Skip system folders - // Skip locations that might not exist - for location in locations.reverse.paths { - if !fileManager.fileExists(atPath: location) { - continue - } - - dispatchGroup.enter() - - do { - - let contents = try fileManager.contentsOfDirectory(atPath: location) - - for item in contents { - - let itemURL = URL(fileURLWithPath: location).appendingPathComponent(item) - let itemName = ("\(item)").pearFormat() - let itemPath = "\(location)/\(itemName)".pearFormat() - - if skipped.contains(where: { itemName.contains($0) }) { - continue - } - - if allPaths.contains(where: { $0 == itemPath }) || allNames.contains(where: { $0 == itemName }) { - continue - } - - collection.append(itemURL) + // Skip locations that might not exist + for location in locations.reverse.paths { + if !fileManager.fileExists(atPath: location) { + continue } - } catch { - printOS("Error processing location:", location, error) - continue + + dispatchGroup.enter() + + do { + + let contents = try fileManager.contentsOfDirectory(atPath: location) + + for item in contents { + + let itemURL = URL(fileURLWithPath: location).appendingPathComponent(item) + let itemName = ("\(item)").pearFormat() + let itemPath = "\(location)/\(itemName)".pearFormat() + + if skipped.contains(where: { itemName.contains($0) }) { + continue + } + + if allPaths.contains(where: { $0 == itemPath }) || allNames.contains(where: { $0 == itemName }) { + continue + } + + collection.append(itemURL) + + } + } catch { + printOS("Error processing location:", location, error) + continue + } + + dispatchGroup.leave() // Leave the dispatch group + } - dispatchGroup.leave() // Leave the dispatch group + // Calculate file details (sizes and icons) + var fileSize: [URL: Int64] = [:] + var fileIcon: [URL: NSImage?] = [:] + var updatedZombieFile = ZombieFile.empty - } - -// let sortedCollection = collection.sorted(by: { $0.absoluteString < $1.absoluteString }) - - // Calculate file details (sizes and icons) - var fileSize: [URL: Int64] = [:] - var fileIcon: [URL: NSImage?] = [:] - var updatedZombieFile = ZombieFile.empty - - for path in collection { - var size: Int64 - var icon: NSImage? = nil - size = totalSizeOnDisk(for: path) - icon = getIconForFileOrFolderNS(atPath: path) - fileSize[path] = size - fileIcon[path] = icon - } - - // Save to appState - dispatchGroup.notify(queue: .main) { - - updateOnMain { - updatedZombieFile.fileSize = fileSize - updatedZombieFile.fileIcon = fileIcon -// appState.selectedZombieItems = Set(sortedCollection) - appState.zombieFile = updatedZombieFile - appState.showProgress = false + for path in collection { + var size: Int64 + var icon: NSImage? = nil + size = totalSizeOnDisk(for: path) + icon = getIconForFileOrFolderNS(atPath: path) + fileSize[path] = size + fileIcon[path] = icon } - } + // Save to appState + dispatchGroup.notify(queue: .main) { - completion() + updateOnMain { + updatedZombieFile.fileSize = fileSize + updatedZombieFile.fileIcon = fileIcon + // appState.selectedZombieItems = Set(sortedCollection) + appState.zombieFile = updatedZombieFile + appState.showProgress = false + } + + } + + completion() } } @@ -640,10 +672,6 @@ func reversePathsSearch(appState: AppState, locations: Locations, completion: @e // Move files to trash using applescript/Finder so it asks for user password if needed func moveFilesToTrash(at fileURLs: [URL], completion: @escaping () -> Void = {}) { @AppStorage("settings.sentinel.enable") var sentinel: Bool = false - if sentinel { - launchctl(load: false) - } - var filesFinder = fileURLs var filesSudo: [URL] = [] @@ -689,107 +717,6 @@ func moveFilesToTrash(at fileURLs: [URL], completion: @escaping () -> Void = {}) } } -//func moveFilesToTrash(at fileURLs: [URL] = [], appState: AppState, undo: Bool = false, completion: @escaping () -> Void = {}) { -// @AppStorage("settings.sentinel.enable") var sentinel: Bool = false -// if sentinel { -// launchctl(load: false) -// } -// -// let home = FileManager.default.homeDirectoryForCurrentUser.path -// -// if undo { -// for url in appState.trashedFiles { -// let file = url.lastPathComponent -// let destinationURL = url.deletingLastPathComponent() -// -// let destinationPath = destinationURL.path -// let trashPath = "\(home)/.Trash/\(file)" -// -// if !destinationPath.starts(with: home) { -// executePrivilegedCommand(launchPath: "/bin/cp", arguments: ["-R"] + [trashPath, destinationPath]) -// } else { -// let task = Process() -// task.launchPath = "/bin/cp" -// task.arguments = ["-R"] + [trashPath, destinationPath] -// -// let pipe = Pipe() -// task.standardOutput = pipe -// task.launch() -// -// task.waitUntilExit() -// } -// } -// -// AudioServicesPlaySystemSound(0xf); -// -// updateOnMain { -// appState.trashedFiles.removeAll() -// } -// -// } else { -// let privilegedURLs = fileURLs.filter { !$0.path.starts(with: home) } -// -// if !privilegedURLs.isEmpty { -// let filePaths = fileURLs.map { $0.path } -// executePrivilegedCommand(launchPath: "/bin/mv", arguments: ["-f"] + filePaths + ["\(home)/.Trash"]) -// } else { -// let filePaths = fileURLs.map { $0.path } -// let task = Process() -// task.launchPath = "/bin/mv" -// task.arguments = ["-f"] + filePaths + ["\(home)/.Trash"] -// -// let pipe = Pipe() -// task.standardOutput = pipe -// task.launch() -// -// task.waitUntilExit() -// } -// -// AudioServicesPlaySystemSound(0x10); -// -// } -// -// completion() -//} - -// Audio -//drag to trash.aif -//AudioServicesPlaySystemSound(0x10); - -//poof item of dock.aif -//AudioServicesPlaySystemSound(0xf); - -//func moveFilesToTrash(at fileURLs: [URL], completion: @escaping () -> Void = {}) { -// @AppStorage("settings.sentinel.enable") var sentinel: Bool = false -// if sentinel { -// launchctl(load: false) -// } -// -// let privilegedURLs = fileURLs.filter { !$0.path.starts(with: home) } -// -// if !privilegedURLs.isEmpty { -// print("Found system files") -// // Use privileged sudo mv function -// let filePaths = privilegedURLs.map { $0.path } -// executePrivilegedCommand(launchPath: "/bin/mv", arguments: filePaths + ["\(home)/.Trash"]) -// completion() -// } else { -// // Use regular shell process for files within the user's home directory -// let filePaths = fileURLs.map { $0.path } -// let task = Process() -// task.launchPath = "/bin/mv" -// task.arguments = filePaths + ["\(home)/.Trash"] -// -// let pipe = Pipe() -// task.standardOutput = pipe -// task.launch() -// -// task.waitUntilExit() -// -// completion() -// } -//} - func isSocketFile(at url: URL) -> Bool { do { @@ -804,8 +731,6 @@ func isSocketFile(at url: URL) -> Bool { } - - // Undo trash action func undoTrash(appState: AppState, completion: @escaping () -> Void = {}) { let scriptSource = """ @@ -844,55 +769,3 @@ func undoTrash(appState: AppState, completion: @escaping () -> Void = {}) { } - - - - - - - - - - -//func isAppRunning(appName: String) -> Bool { -// let task = Process() -// task.executableURL = URL(fileURLWithPath: "/usr/bin/pgrep") -// task.arguments = ["-af", appName] -// -// let outputPipe = Pipe() -// task.standardOutput = outputPipe -// -// do { -// try task.run() -// } catch { -// printOS("Failed to run task: \(error)") -// return false -// } -// -// let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() -// let output = String(decoding: outputData, as: UTF8.self) -// -// if output.isEmpty { -// return false -// } else { -// return true -// } -//} -// -//// Kill app if running -//func killApp2(appName: String, completion: @escaping () -> Void = {}) { -// if isAppRunning(appName: appName) { -// let task = Process() -// task.executableURL = URL(fileURLWithPath: "/usr/bin/pkill") -// task.arguments = ["-af", appName] -// -// do { -// try task.run() -// } catch { -// printOS("Failed to run task: \(error)") -// return -// } -// -// } -// completion() -//} diff --git a/Pearcleaner/Logic/Shapes.swift b/Pearcleaner/Logic/Shapes.swift deleted file mode 100644 index f80c6da..0000000 --- a/Pearcleaner/Logic/Shapes.swift +++ /dev/null @@ -1,599 +0,0 @@ -//// -//// Shapes.swift -//// Pearcleaner -//// -//// Created by Alin Lupascu on 11/11/23. -//// -// -//import Foundation -//import SwiftUI -// -//// Pear Logo -//struct Pear: Shape { -// func path(in rect: CGRect) -> Path { -// var path = Path() -// let width = rect.size.width -// let height = rect.size.height -// path.move(to: CGPoint(x: 0.5985*width, y: 0.045*height)) -// path.addCurve(to: CGPoint(x: 0.508*width, y: 0.141*height), control1: CGPoint(x: 0.571*width, y: 0.0635*height), control2: CGPoint(x: 0.521*width, y: 0.116*height)) -// path.addLine(to: CGPoint(x: 0.4985*width, y: 0.159*height)) -// path.addLine(to: CGPoint(x: 0.4725*width, y: 0.1325*height)) -// path.addCurve(to: CGPoint(x: 0.318*width, y: 0.0975*height), control1: CGPoint(x: 0.433*width, y: 0.092*height), control2: CGPoint(x: 0.383*width, y: 0.0805*height)) -// path.addCurve(to: CGPoint(x: 0.2105*width, y: 0.15*height), control1: CGPoint(x: 0.2875*width, y: 0.1055*height), control2: CGPoint(x: 0.231*width, y: 0.133*height)) -// path.addLine(to: CGPoint(x: 0.1985*width, y: 0.16*height)) -// path.addLine(to: CGPoint(x: 0.2125*width, y: 0.18*height)) -// path.addCurve(to: CGPoint(x: 0.316*width, y: 0.273*height), control1: CGPoint(x: 0.2335*width, y: 0.2105*height), control2: CGPoint(x: 0.287*width, y: 0.2585*height)) -// path.addCurve(to: CGPoint(x: 0.371*width, y: 0.289*height), control1: CGPoint(x: 0.331*width, y: 0.2805*height), control2: CGPoint(x: 0.354*width, y: 0.287*height)) -// path.addLine(to: CGPoint(x: 0.4*width, y: 0.2915*height)) -// path.addLine(to: CGPoint(x: 0.375*width, y: 0.318*height)) -// path.addCurve(to: CGPoint(x: 0.3195*width, y: 0.443*height), control1: CGPoint(x: 0.3415*width, y: 0.3535*height), control2: CGPoint(x: 0.326*width, y: 0.388*height)) -// path.addCurve(to: CGPoint(x: 0.2635*width, y: 0.564*height), control1: CGPoint(x: 0.3125*width, y: 0.501*height), control2: CGPoint(x: 0.302*width, y: 0.5235*height)) -// path.addCurve(to: CGPoint(x: 0.19*width, y: 0.7345*height), control1: CGPoint(x: 0.21*width, y: 0.62*height), control2: CGPoint(x: 0.19*width, y: 0.6665*height)) -// path.addCurve(to: CGPoint(x: 0.3235*width, y: 0.949*height), control1: CGPoint(x: 0.19*width, y: 0.8305*height), control2: CGPoint(x: 0.2385*width, y: 0.9085*height)) -// path.addCurve(to: CGPoint(x: 0.4125*width, y: 0.9675*height), control1: CGPoint(x: 0.3605*width, y: 0.9665*height), control2: CGPoint(x: 0.365*width, y: 0.9675*height)) -// path.addCurve(to: CGPoint(x: 0.4875*width, y: 0.958*height), control1: CGPoint(x: 0.449*width, y: 0.9675*height), control2: CGPoint(x: 0.4695*width, y: 0.965*height)) -// path.addCurve(to: CGPoint(x: 0.547*width, y: 0.9595*height), control1: CGPoint(x: 0.5125*width, y: 0.949*height), control2: CGPoint(x: 0.513*width, y: 0.949*height)) -// path.addCurve(to: CGPoint(x: 0.759*width, y: 0.9135*height), control1: CGPoint(x: 0.6225*width, y: 0.983*height), control2: CGPoint(x: 0.702*width, y: 0.9655*height)) -// path.addCurve(to: CGPoint(x: 0.7975*width, y: 0.817*height), control1: CGPoint(x: 0.8085*width, y: 0.8675*height), control2: CGPoint(x: 0.8225*width, y: 0.8335*height)) -// path.addCurve(to: CGPoint(x: 0.7615*width, y: 0.817*height), control1: CGPoint(x: 0.786*width, y: 0.8095*height), control2: CGPoint(x: 0.7835*width, y: 0.8095*height)) -// path.addCurve(to: CGPoint(x: 0.661*width, y: 0.8015*height), control1: CGPoint(x: 0.723*width, y: 0.83*height), control2: CGPoint(x: 0.6935*width, y: 0.8255*height)) -// path.addCurve(to: CGPoint(x: 0.634*width, y: 0.6745*height), control1: CGPoint(x: 0.6245*width, y: 0.7745*height), control2: CGPoint(x: 0.611*width, y: 0.711*height)) -// path.addCurve(to: CGPoint(x: 0.6545*width, y: 0.618*height), control1: CGPoint(x: 0.659*width, y: 0.6345*height), control2: CGPoint(x: 0.6605*width, y: 0.631*height)) -// path.addCurve(to: CGPoint(x: 0.6325*width, y: 0.6*height), control1: CGPoint(x: 0.651*width, y: 0.611*height), control2: CGPoint(x: 0.6415*width, y: 0.603*height)) -// path.addCurve(to: CGPoint(x: 0.575*width, y: 0.5175*height), control1: CGPoint(x: 0.603*width, y: 0.59*height), control2: CGPoint(x: 0.575*width, y: 0.55*height)) -// path.addCurve(to: CGPoint(x: 0.6595*width, y: 0.4255*height), control1: CGPoint(x: 0.575*width, y: 0.4685*height), control2: CGPoint(x: 0.609*width, y: 0.4315*height)) -// path.addCurve(to: CGPoint(x: 0.691*width, y: 0.4095*height), control1: CGPoint(x: 0.6755*width, y: 0.4235*height), control2: CGPoint(x: 0.684*width, y: 0.419*height)) -// path.addCurve(to: CGPoint(x: 0.691*width, y: 0.3745*height), control1: CGPoint(x: 0.701*width, y: 0.3965*height), control2: CGPoint(x: 0.701*width, y: 0.396*height)) -// path.addCurve(to: CGPoint(x: 0.565*width, y: 0.2645*height), control1: CGPoint(x: 0.667*width, y: 0.3215*height), control2: CGPoint(x: 0.6175*width, y: 0.278*height)) -// path.addLine(to: CGPoint(x: 0.54*width, y: 0.258*height)) -// path.addLine(to: CGPoint(x: 0.54*width, y: 0.2355*height)) -// path.addCurve(to: CGPoint(x: 0.6215*width, y: 0.102*height), control1: CGPoint(x: 0.54*width, y: 0.192*height), control2: CGPoint(x: 0.5745*width, y: 0.1355*height)) -// path.addCurve(to: CGPoint(x: 0.66*width, y: 0.0575*height), control1: CGPoint(x: 0.653*width, y: 0.0795*height), control2: CGPoint(x: 0.66*width, y: 0.0715*height)) -// path.addCurve(to: CGPoint(x: 0.6315*width, y: 0.03*height), control1: CGPoint(x: 0.66*width, y: 0.044*height), control2: CGPoint(x: 0.6455*width, y: 0.03*height)) -// path.addCurve(to: CGPoint(x: 0.5985*width, y: 0.045*height), control1: CGPoint(x: 0.626*width, y: 0.03*height), control2: CGPoint(x: 0.6115*width, y: 0.037*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 0.4155*width, y: 0.1615*height)) -// path.addCurve(to: CGPoint(x: 0.444*width, y: 0.19*height), control1: CGPoint(x: 0.4245*width, y: 0.1675*height), control2: CGPoint(x: 0.4375*width, y: 0.1805*height)) -// path.addLine(to: CGPoint(x: 0.4555*width, y: 0.2075*height)) -// path.addLine(to: CGPoint(x: 0.445*width, y: 0.216*height)) -// path.addCurve(to: CGPoint(x: 0.369*width, y: 0.23*height), control1: CGPoint(x: 0.4265*width, y: 0.231*height), control2: CGPoint(x: 0.397*width, y: 0.2365*height)) -// path.addCurve(to: CGPoint(x: 0.285*width, y: 0.175*height), control1: CGPoint(x: 0.345*width, y: 0.224*height), control2: CGPoint(x: 0.285*width, y: 0.185*height)) -// path.addCurve(to: CGPoint(x: 0.2865*width, y: 0.17*height), control1: CGPoint(x: 0.285*width, y: 0.1725*height), control2: CGPoint(x: 0.2855*width, y: 0.17*height)) -// path.addCurve(to: CGPoint(x: 0.315*width, y: 0.16*height), control1: CGPoint(x: 0.287*width, y: 0.17*height), control2: CGPoint(x: 0.3*width, y: 0.1655*height)) -// path.addCurve(to: CGPoint(x: 0.4155*width, y: 0.1615*height), control1: CGPoint(x: 0.3535*width, y: 0.1465*height), control2: CGPoint(x: 0.394*width, y: 0.147*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 0.553*width, y: 0.3225*height)) -// path.addCurve(to: CGPoint(x: 0.62*width, y: 0.369*height), control1: CGPoint(x: 0.5755*width, y: 0.329*height), control2: CGPoint(x: 0.62*width, y: 0.3605*height)) -// path.addCurve(to: CGPoint(x: 0.599*width, y: 0.382*height), control1: CGPoint(x: 0.62*width, y: 0.371*height), control2: CGPoint(x: 0.6105*width, y: 0.377*height)) -// path.addCurve(to: CGPoint(x: 0.5295*width, y: 0.453*height), control1: CGPoint(x: 0.572*width, y: 0.3945*height), control2: CGPoint(x: 0.5445*width, y: 0.4225*height)) -// path.addCurve(to: CGPoint(x: 0.5315*width, y: 0.5815*height), control1: CGPoint(x: 0.512*width, y: 0.4895*height), control2: CGPoint(x: 0.5125*width, y: 0.5425*height)) -// path.addCurve(to: CGPoint(x: 0.565*width, y: 0.626*height), control1: CGPoint(x: 0.5395*width, y: 0.597*height), control2: CGPoint(x: 0.5545*width, y: 0.6175*height)) -// path.addCurve(to: CGPoint(x: 0.576*width, y: 0.6575*height), control1: CGPoint(x: 0.584*width, y: 0.642*height), control2: CGPoint(x: 0.584*width, y: 0.6425*height)) -// path.addCurve(to: CGPoint(x: 0.572*width, y: 0.7725*height), control1: CGPoint(x: 0.5645*width, y: 0.68*height), control2: CGPoint(x: 0.562*width, y: 0.744*height)) -// path.addCurve(to: CGPoint(x: 0.6785*width, y: 0.8755*height), control1: CGPoint(x: 0.5875*width, y: 0.8195*height), control2: CGPoint(x: 0.632*width, y: 0.862*height)) -// path.addCurve(to: CGPoint(x: 0.6665*width, y: 0.902*height), control1: CGPoint(x: 0.7045*width, y: 0.883*height), control2: CGPoint(x: 0.701*width, y: 0.8905*height)) -// path.addCurve(to: CGPoint(x: 0.546*width, y: 0.897*height), control1: CGPoint(x: 0.6305*width, y: 0.9145*height), control2: CGPoint(x: 0.585*width, y: 0.9125*height)) -// path.addCurve(to: CGPoint(x: 0.498*width, y: 0.89*height), control1: CGPoint(x: 0.517*width, y: 0.885*height), control2: CGPoint(x: 0.5115*width, y: 0.8845*height)) -// path.addCurve(to: CGPoint(x: 0.405*width, y: 0.909*height), control1: CGPoint(x: 0.451*width, y: 0.909*height), control2: CGPoint(x: 0.4415*width, y: 0.911*height)) -// path.addCurve(to: CGPoint(x: 0.2925*width, y: 0.8535*height), control1: CGPoint(x: 0.3585*width, y: 0.9065*height), control2: CGPoint(x: 0.3245*width, y: 0.8895*height)) -// path.addCurve(to: CGPoint(x: 0.2485*width, y: 0.734*height), control1: CGPoint(x: 0.2625*width, y: 0.8205*height), control2: CGPoint(x: 0.2485*width, y: 0.7825*height)) -// path.addCurve(to: CGPoint(x: 0.3115*width, y: 0.597*height), control1: CGPoint(x: 0.2485*width, y: 0.6825*height), control2: CGPoint(x: 0.267*width, y: 0.642*height)) -// path.addCurve(to: CGPoint(x: 0.375*width, y: 0.465*height), control1: CGPoint(x: 0.3535*width, y: 0.5545*height), control2: CGPoint(x: 0.3695*width, y: 0.5215*height)) -// path.addCurve(to: CGPoint(x: 0.385*width, y: 0.4075*height), control1: CGPoint(x: 0.3775*width, y: 0.441*height), control2: CGPoint(x: 0.382*width, y: 0.415*height)) -// path.addCurve(to: CGPoint(x: 0.438*width, y: 0.3395*height), control1: CGPoint(x: 0.394*width, y: 0.3835*height), control2: CGPoint(x: 0.4195*width, y: 0.351*height)) -// path.addCurve(to: CGPoint(x: 0.553*width, y: 0.3225*height), control1: CGPoint(x: 0.4785*width, y: 0.3145*height), control2: CGPoint(x: 0.5105*width, y: 0.31*height)) -// path.closeSubpath() -// return path -// } -//} -// -//// Pear text -//struct Pearcleaner: Shape { -// func path(in rect: CGRect) -> Path { -// var path = Path() -// let width = rect.size.width -// let height = rect.size.height -// path.move(to: CGPoint(x: 1.10767*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.10292*width, y: -0.22248*height), control1: CGPoint(x: 1.10519*width, y: -0.23943*height), control2: CGPoint(x: 1.10375*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.09362*width, y: -0.14302*height), control1: CGPoint(x: 1.10044*width, y: -0.1854*height), control2: CGPoint(x: 1.09755*width, y: -0.16315*height)) -// path.addCurve(to: CGPoint(x: 1.0744*width, y: -0.09005*height), control1: CGPoint(x: 1.08783*width, y: -0.11442*height), control2: CGPoint(x: 1.07977*width, y: -0.09005*height)) -// path.addCurve(to: CGPoint(x: 1.09052*width, y: -0.24684*height), control1: CGPoint(x: 1.07977*width, y: -0.10594*height), control2: CGPoint(x: 1.08928*width, y: -0.16103*height)) -// path.addCurve(to: CGPoint(x: 1.05869*width, y: -0.43648*height), control1: CGPoint(x: 1.09217*width, y: -0.36126*height), control2: CGPoint(x: 1.0775*width, y: -0.43648*height)) -// path.addCurve(to: CGPoint(x: 1.02955*width, y: -0.36868*height), control1: CGPoint(x: 1.04546*width, y: -0.43648*height), control2: CGPoint(x: 1.03513*width, y: -0.39834*height)) -// path.addLine(to: CGPoint(x: 1.03058*width, y: -0.40364*height)) -// path.addCurve(to: CGPoint(x: 1.02686*width, y: -0.42377*height), control1: CGPoint(x: 1.031*width, y: -0.41529*height), control2: CGPoint(x: 1.02934*width, y: -0.42377*height)) -// path.addLine(to: CGPoint(x: 1.01054*width, y: -0.42377*height)) -// path.addCurve(to: CGPoint(x: 1.00578*width, y: -0.40258*height), control1: CGPoint(x: 1.00805*width, y: -0.42377*height), control2: CGPoint(x: 1.00619*width, y: -0.41529*height)) -// path.addLine(to: CGPoint(x: 0.98532*width, y: 0.26062*height)) -// path.addCurve(to: CGPoint(x: 0.98883*width, y: 0.2818*height), control1: CGPoint(x: 0.98491*width, y: 0.27227*height), control2: CGPoint(x: 0.98656*width, y: 0.2818*height)) -// path.addLine(to: CGPoint(x: 1.00516*width, y: 0.2818*height)) -// path.addCurve(to: CGPoint(x: 1.01012*width, y: 0.25956*height), control1: CGPoint(x: 1.00805*width, y: 0.2818*height), control2: CGPoint(x: 1.00971*width, y: 0.27333*height)) -// path.addLine(to: CGPoint(x: 1.02087*width, y: -0.09111*height)) -// path.addCurve(to: CGPoint(x: 1.05146*width, y: -0.31888*height), control1: CGPoint(x: 1.02583*width, y: -0.2532*height), control2: CGPoint(x: 1.03926*width, y: -0.31888*height)) -// path.addCurve(to: CGPoint(x: 1.06282*width, y: -0.20023*height), control1: CGPoint(x: 1.06262*width, y: -0.31888*height), control2: CGPoint(x: 1.06572*width, y: -0.26379*height)) -// path.addCurve(to: CGPoint(x: 1.04298*width, y: -0.09429*height), control1: CGPoint(x: 1.06034*width, y: -0.14302*height), control2: CGPoint(x: 1.05352*width, y: -0.09429*height)) -// path.addCurve(to: CGPoint(x: 1.02976*width, y: -0.09111*height), control1: CGPoint(x: 1.03203*width, y: -0.10594*height), control2: CGPoint(x: 1.03058*width, y: -0.1017*height)) -// path.addLine(to: CGPoint(x: 1.02562*width, y: -0.04132*height)) -// path.addCurve(to: CGPoint(x: 1.03017*width, y: -0.00848*height), control1: CGPoint(x: 1.02438*width, y: -0.02649*height), control2: CGPoint(x: 1.02542*width, y: -0.01801*height)) -// path.addCurve(to: CGPoint(x: 1.05538*width, y: 0.01271*height), control1: CGPoint(x: 1.03348*width, y: -0.00106*height), control2: CGPoint(x: 1.04154*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.11801*width, y: -0.19917*height), control1: CGPoint(x: 1.08411*width, y: 0.01271*height), control2: CGPoint(x: 1.10891*width, y: -0.05615*height)) -// path.addCurve(to: CGPoint(x: 1.11573*width, y: -0.22248*height), control1: CGPoint(x: 1.11883*width, y: -0.21188*height), control2: CGPoint(x: 1.1178*width, y: -0.2193*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.18208*width, y: -0.214*height)) -// path.addCurve(to: CGPoint(x: 1.14695*width, y: -0.09641*height), control1: CGPoint(x: 1.17423*width, y: -0.13137*height), control2: CGPoint(x: 1.15956*width, y: -0.09641*height)) -// path.addCurve(to: CGPoint(x: 1.12959*width, y: -0.14302*height), control1: CGPoint(x: 1.13806*width, y: -0.09641*height), control2: CGPoint(x: 1.13248*width, y: -0.11442*height)) -// path.addCurve(to: CGPoint(x: 1.17712*width, y: -0.28604*height), control1: CGPoint(x: 1.14757*width, y: -0.1409*height), control2: CGPoint(x: 1.17237*width, y: -0.18434*height)) -// path.addCurve(to: CGPoint(x: 1.14984*width, y: -0.43648*height), control1: CGPoint(x: 1.18126*width, y: -0.37186*height), control2: CGPoint(x: 1.16844*width, y: -0.43648*height)) -// path.addCurve(to: CGPoint(x: 1.10148*width, y: -0.23095*height), control1: CGPoint(x: 1.12918*width, y: -0.43648*height), control2: CGPoint(x: 1.10644*width, y: -0.35702*height)) -// path.addCurve(to: CGPoint(x: 1.1424*width, y: 0.01271*height), control1: CGPoint(x: 1.0959*width, y: -0.09323*height), control2: CGPoint(x: 1.11533*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.198*width, y: -0.19917*height), control1: CGPoint(x: 1.16824*width, y: 0.01271*height), control2: CGPoint(x: 1.18973*width, y: -0.08263*height)) -// path.addCurve(to: CGPoint(x: 1.19573*width, y: -0.22248*height), control1: CGPoint(x: 1.19883*width, y: -0.21082*height), control2: CGPoint(x: 1.19821*width, y: -0.21824*height)) -// path.addLine(to: CGPoint(x: 1.18766*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.18208*width, y: -0.214*height), control1: CGPoint(x: 1.18518*width, y: -0.23943*height), control2: CGPoint(x: 1.18415*width, y: -0.23519*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.12773*width, y: -0.20659*height)) -// path.addCurve(to: CGPoint(x: 1.12794*width, y: -0.214*height), control1: CGPoint(x: 1.12773*width, y: -0.20871*height), control2: CGPoint(x: 1.12794*width, y: -0.21188*height)) -// path.addCurve(to: CGPoint(x: 1.14922*width, y: -0.32736*height), control1: CGPoint(x: 1.13021*width, y: -0.27863*height), control2: CGPoint(x: 1.14137*width, y: -0.32736*height)) -// path.addCurve(to: CGPoint(x: 1.15563*width, y: -0.2977*height), control1: CGPoint(x: 1.15336*width, y: -0.32736*height), control2: CGPoint(x: 1.15563*width, y: -0.31465*height)) -// path.addCurve(to: CGPoint(x: 1.12773*width, y: -0.20659*height), control1: CGPoint(x: 1.15563*width, y: -0.25426*height), control2: CGPoint(x: 1.14178*width, y: -0.20976*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.21075*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.24237*width, y: -0.0678*height), control1: CGPoint(x: 1.22604*width, y: 0.01271*height), control2: CGPoint(x: 1.23782*width, y: -0.0339*height)) -// path.addCurve(to: CGPoint(x: 1.26552*width, y: 0.01271*height), control1: CGPoint(x: 1.24547*width, y: -0.01907*height), control2: CGPoint(x: 1.25373*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.30644*width, y: -0.19917*height), control1: CGPoint(x: 1.28866*width, y: 0.01271*height), control2: CGPoint(x: 1.30189*width, y: -0.11018*height)) -// path.addCurve(to: CGPoint(x: 1.30416*width, y: -0.22248*height), control1: CGPoint(x: 1.30706*width, y: -0.21082*height), control2: CGPoint(x: 1.30664*width, y: -0.21824*height)) -// path.addLine(to: CGPoint(x: 1.2961*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.29135*width, y: -0.22248*height), control1: CGPoint(x: 1.29362*width, y: -0.23943*height), control2: CGPoint(x: 1.29218*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.27192*width, y: -0.09429*height), control1: CGPoint(x: 1.28846*width, y: -0.18328*height), control2: CGPoint(x: 1.28143*width, y: -0.09429*height)) -// path.addCurve(to: CGPoint(x: 1.26717*width, y: -0.18434*height), control1: CGPoint(x: 1.26614*width, y: -0.09429*height), control2: CGPoint(x: 1.26552*width, y: -0.12819*height)) -// path.addLine(to: CGPoint(x: 1.27378*width, y: -0.40258*height)) -// path.addCurve(to: CGPoint(x: 1.27027*width, y: -0.42377*height), control1: CGPoint(x: 1.2742*width, y: -0.41423*height), control2: CGPoint(x: 1.27275*width, y: -0.42377*height)) -// path.addLine(to: CGPoint(x: 1.25373*width, y: -0.42377*height)) -// path.addCurve(to: CGPoint(x: 1.24898*width, y: -0.40258*height), control1: CGPoint(x: 1.25125*width, y: -0.42377*height), control2: CGPoint(x: 1.24939*width, y: -0.41529*height)) -// path.addLine(to: CGPoint(x: 1.24877*width, y: -0.39622*height)) -// path.addCurve(to: CGPoint(x: 1.22769*width, y: -0.43648*height), control1: CGPoint(x: 1.24547*width, y: -0.41105*height), control2: CGPoint(x: 1.23885*width, y: -0.43648*height)) -// path.addCurve(to: CGPoint(x: 1.18264*width, y: -0.24473*height), control1: CGPoint(x: 1.20765*width, y: -0.43648*height), control2: CGPoint(x: 1.18925*width, y: -0.35596*height)) -// path.addCurve(to: CGPoint(x: 1.21075*width, y: 0.01271*height), control1: CGPoint(x: 1.1752*width, y: -0.11865*height), control2: CGPoint(x: 1.18512*width, y: 0.01271*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.24567*width, y: -0.29346*height)) -// path.addCurve(to: CGPoint(x: 1.21901*width, y: -0.09429*height), control1: CGPoint(x: 1.24278*width, y: -0.18858*height), control2: CGPoint(x: 1.23183*width, y: -0.09429*height)) -// path.addCurve(to: CGPoint(x: 1.20806*width, y: -0.21294*height), control1: CGPoint(x: 1.2093*width, y: -0.09429*height), control2: CGPoint(x: 1.20496*width, y: -0.15044*height)) -// path.addCurve(to: CGPoint(x: 1.23245*width, y: -0.3263*height), control1: CGPoint(x: 1.21178*width, y: -0.28604*height), control2: CGPoint(x: 1.22232*width, y: -0.3263*height)) -// path.addCurve(to: CGPoint(x: 1.24567*width, y: -0.29346*height), control1: CGPoint(x: 1.23968*width, y: -0.3263*height), control2: CGPoint(x: 1.24361*width, y: -0.30511*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.36936*width, y: -0.22248*height)) -// path.addCurve(to: CGPoint(x: 1.34807*width, y: -0.09429*height), control1: CGPoint(x: 1.36626*width, y: -0.17904*height), control2: CGPoint(x: 1.35799*width, y: -0.09429*height)) -// path.addCurve(to: CGPoint(x: 1.34228*width, y: -0.16527*height), control1: CGPoint(x: 1.34331*width, y: -0.09429*height), control2: CGPoint(x: 1.34001*width, y: -0.11442*height)) -// path.addCurve(to: CGPoint(x: 1.34972*width, y: -0.31253*height), control1: CGPoint(x: 1.34497*width, y: -0.22777*height), control2: CGPoint(x: 1.34931*width, y: -0.30405*height)) -// path.addCurve(to: CGPoint(x: 1.34393*width, y: -0.38245*height), control1: CGPoint(x: 1.35117*width, y: -0.34749*height), control2: CGPoint(x: 1.35075*width, y: -0.36656*height)) -// path.addCurve(to: CGPoint(x: 1.315*width, y: -0.44813*height), control1: CGPoint(x: 1.3336*width, y: -0.40576*height), control2: CGPoint(x: 1.32099*width, y: -0.42589*height)) -// path.addCurve(to: CGPoint(x: 1.30322*width, y: -0.52017*height), control1: CGPoint(x: 1.31541*width, y: -0.48945*height), control2: CGPoint(x: 1.31128*width, y: -0.52441*height)) -// path.addCurve(to: CGPoint(x: 1.29495*width, y: -0.40258*height), control1: CGPoint(x: 1.29392*width, y: -0.51594*height), control2: CGPoint(x: 1.28999*width, y: -0.46508*height)) -// path.addCurve(to: CGPoint(x: 1.29061*width, y: -0.21506*height), control1: CGPoint(x: 1.29619*width, y: -0.3655*height), control2: CGPoint(x: 1.29537*width, y: -0.27969*height)) -// path.addCurve(to: CGPoint(x: 1.29206*width, y: -0.19493*height), control1: CGPoint(x: 1.28979*width, y: -0.20553*height), control2: CGPoint(x: 1.2902*width, y: -0.19811*height)) -// path.addLine(to: CGPoint(x: 1.30177*width, y: -0.17586*height)) -// path.addCurve(to: CGPoint(x: 1.30549*width, y: -0.18434*height), control1: CGPoint(x: 1.30363*width, y: -0.17268*height), control2: CGPoint(x: 1.30487*width, y: -0.17374*height)) -// path.addCurve(to: CGPoint(x: 1.31211*width, y: -0.35279*height), control1: CGPoint(x: 1.30859*width, y: -0.23519*height), control2: CGPoint(x: 1.31087*width, y: -0.29346*height)) -// path.addLine(to: CGPoint(x: 1.32017*width, y: -0.33372*height)) -// path.addCurve(to: CGPoint(x: 1.32347*width, y: -0.29664*height), control1: CGPoint(x: 1.32347*width, y: -0.3263*height), control2: CGPoint(x: 1.32451*width, y: -0.31783*height)) -// path.addCurve(to: CGPoint(x: 1.31583*width, y: -0.14408*height), control1: CGPoint(x: 1.32079*width, y: -0.23625*height), control2: CGPoint(x: 1.31748*width, y: -0.19387*height)) -// path.addCurve(to: CGPoint(x: 1.3429*width, y: 0.01271*height), control1: CGPoint(x: 1.31335*width, y: -0.06145*height), control2: CGPoint(x: 1.32554*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.38444*width, y: -0.19917*height), control1: CGPoint(x: 1.36543*width, y: 0.01271*height), control2: CGPoint(x: 1.3799*width, y: -0.11018*height)) -// path.addCurve(to: CGPoint(x: 1.38217*width, y: -0.22248*height), control1: CGPoint(x: 1.38506*width, y: -0.21082*height), control2: CGPoint(x: 1.38465*width, y: -0.21824*height)) -// path.addLine(to: CGPoint(x: 1.37411*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.36936*width, y: -0.22248*height), control1: CGPoint(x: 1.37163*width, y: -0.23943*height), control2: CGPoint(x: 1.37018*width, y: -0.23519*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.45862*width, y: -0.22248*height)) -// path.addLine(to: CGPoint(x: 1.45056*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.44539*width, y: -0.21824*height), control1: CGPoint(x: 1.44787*width, y: -0.23943*height), control2: CGPoint(x: 1.44705*width, y: -0.23413*height)) -// path.addCurve(to: CGPoint(x: 1.41253*width, y: -0.09429*height), control1: CGPoint(x: 1.4363*width, y: -0.13666*height), control2: CGPoint(x: 1.42514*width, y: -0.09429*height)) -// path.addCurve(to: CGPoint(x: 1.39455*width, y: -0.22036*height), control1: CGPoint(x: 1.39703*width, y: -0.09429*height), control2: CGPoint(x: 1.39166*width, y: -0.15573*height)) -// path.addCurve(to: CGPoint(x: 1.41956*width, y: -0.32948*height), control1: CGPoint(x: 1.39744*width, y: -0.28498*height), control2: CGPoint(x: 1.40778*width, y: -0.32948*height)) -// path.addCurve(to: CGPoint(x: 1.42968*width, y: -0.31783*height), control1: CGPoint(x: 1.42348*width, y: -0.32948*height), control2: CGPoint(x: 1.427*width, y: -0.32418*height)) -// path.addCurve(to: CGPoint(x: 1.43506*width, y: -0.32312*height), control1: CGPoint(x: 1.43216*width, y: -0.31253*height), control2: CGPoint(x: 1.4332*width, y: -0.31253*height)) -// path.addLine(to: CGPoint(x: 1.44126*width, y: -0.3602*height)) -// path.addCurve(to: CGPoint(x: 1.44043*width, y: -0.38987*height), control1: CGPoint(x: 1.44271*width, y: -0.36974*height), control2: CGPoint(x: 1.44312*width, y: -0.37609*height)) -// path.addCurve(to: CGPoint(x: 1.4148*width, y: -0.43648*height), control1: CGPoint(x: 1.43568*width, y: -0.41211*height), control2: CGPoint(x: 1.42576*width, y: -0.43648*height)) -// path.addCurve(to: CGPoint(x: 1.36768*width, y: -0.2246*height), control1: CGPoint(x: 1.39455*width, y: -0.43648*height), control2: CGPoint(x: 1.37202*width, y: -0.3549*height)) -// path.addCurve(to: CGPoint(x: 1.40592*width, y: 0.01271*height), control1: CGPoint(x: 1.36355*width, y: -0.09535*height), control2: CGPoint(x: 1.37926*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.46089*width, y: -0.19917*height), control1: CGPoint(x: 1.42968*width, y: 0.01271*height), control2: CGPoint(x: 1.45139*width, y: -0.0731*height)) -// path.addCurve(to: CGPoint(x: 1.45862*width, y: -0.22248*height), control1: CGPoint(x: 1.46172*width, y: -0.21082*height), control2: CGPoint(x: 1.4611*width, y: -0.21824*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.52396*width, y: -0.22354*height)) -// path.addLine(to: CGPoint(x: 1.5159*width, y: -0.23625*height)) -// path.addCurve(to: CGPoint(x: 1.51114*width, y: -0.22566*height), control1: CGPoint(x: 1.51362*width, y: -0.23943*height), control2: CGPoint(x: 1.51218*width, y: -0.23731*height)) -// path.addCurve(to: CGPoint(x: 1.48428*width, y: -0.09641*height), control1: CGPoint(x: 1.50164*width, y: -0.12395*height), control2: CGPoint(x: 1.4911*width, y: -0.09641*height)) -// path.addCurve(to: CGPoint(x: 1.46692*width, y: -0.25214*height), control1: CGPoint(x: 1.47312*width, y: -0.09641*height), control2: CGPoint(x: 1.46754*width, y: -0.17374*height)) -// path.addCurve(to: CGPoint(x: 1.51301*width, y: -0.44284*height), control1: CGPoint(x: 1.48325*width, y: -0.27333*height), control2: CGPoint(x: 1.50267*width, y: -0.33372*height)) -// path.addCurve(to: CGPoint(x: 1.49813*width, y: -0.70133*height), control1: CGPoint(x: 1.52231*width, y: -0.54242*height), control2: CGPoint(x: 1.52603*width, y: -0.70133*height)) -// path.addCurve(to: CGPoint(x: 1.44584*width, y: -0.36762*height), control1: CGPoint(x: 1.47705*width, y: -0.70133*height), control2: CGPoint(x: 1.45452*width, y: -0.57314*height)) -// path.addCurve(to: CGPoint(x: 1.47829*width, y: 0.01271*height), control1: CGPoint(x: 1.43922*width, y: -0.21188*height), control2: CGPoint(x: 1.44294*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.52499*width, y: -0.18964*height), control1: CGPoint(x: 1.49854*width, y: 0.01271*height), control2: CGPoint(x: 1.51569*width, y: -0.06145*height)) -// path.addCurve(to: CGPoint(x: 1.52396*width, y: -0.22354*height), control1: CGPoint(x: 1.52665*width, y: -0.21082*height), control2: CGPoint(x: 1.52685*width, y: -0.21824*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.46878*width, y: -0.34961*height)) -// path.addCurve(to: CGPoint(x: 1.49523*width, y: -0.59221*height), control1: CGPoint(x: 1.47394*width, y: -0.47462*height), control2: CGPoint(x: 1.488*width, y: -0.5901*height)) -// path.addCurve(to: CGPoint(x: 1.49296*width, y: -0.46508*height), control1: CGPoint(x: 1.5006*width, y: -0.59327*height), control2: CGPoint(x: 1.49957*width, y: -0.52759*height)) -// path.addCurve(to: CGPoint(x: 1.46878*width, y: -0.34961*height), control1: CGPoint(x: 1.48655*width, y: -0.40258*height), control2: CGPoint(x: 1.47684*width, y: -0.36444*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.58615*width, y: -0.214*height)) -// path.addCurve(to: CGPoint(x: 1.55101*width, y: -0.09641*height), control1: CGPoint(x: 1.5783*width, y: -0.13137*height), control2: CGPoint(x: 1.56362*width, y: -0.09641*height)) -// path.addCurve(to: CGPoint(x: 1.53366*width, y: -0.14302*height), control1: CGPoint(x: 1.54213*width, y: -0.09641*height), control2: CGPoint(x: 1.53655*width, y: -0.11442*height)) -// path.addCurve(to: CGPoint(x: 1.58119*width, y: -0.28604*height), control1: CGPoint(x: 1.55164*width, y: -0.1409*height), control2: CGPoint(x: 1.57644*width, y: -0.18434*height)) -// path.addCurve(to: CGPoint(x: 1.55391*width, y: -0.43648*height), control1: CGPoint(x: 1.58533*width, y: -0.37186*height), control2: CGPoint(x: 1.57251*width, y: -0.43648*height)) -// path.addCurve(to: CGPoint(x: 1.50555*width, y: -0.23095*height), control1: CGPoint(x: 1.53324*width, y: -0.43648*height), control2: CGPoint(x: 1.51051*width, y: -0.35702*height)) -// path.addCurve(to: CGPoint(x: 1.54647*width, y: 0.01271*height), control1: CGPoint(x: 1.49997*width, y: -0.09323*height), control2: CGPoint(x: 1.5194*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.60207*width, y: -0.19917*height), control1: CGPoint(x: 1.57231*width, y: 0.01271*height), control2: CGPoint(x: 1.5938*width, y: -0.08263*height)) -// path.addCurve(to: CGPoint(x: 1.59979*width, y: -0.22248*height), control1: CGPoint(x: 1.60289*width, y: -0.21082*height), control2: CGPoint(x: 1.60227*width, y: -0.21824*height)) -// path.addLine(to: CGPoint(x: 1.59173*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.58615*width, y: -0.214*height), control1: CGPoint(x: 1.58925*width, y: -0.23943*height), control2: CGPoint(x: 1.58822*width, y: -0.23519*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.5318*width, y: -0.20659*height)) -// path.addCurve(to: CGPoint(x: 1.532*width, y: -0.214*height), control1: CGPoint(x: 1.5318*width, y: -0.20871*height), control2: CGPoint(x: 1.532*width, y: -0.21188*height)) -// path.addCurve(to: CGPoint(x: 1.55329*width, y: -0.32736*height), control1: CGPoint(x: 1.53428*width, y: -0.27863*height), control2: CGPoint(x: 1.54544*width, y: -0.32736*height)) -// path.addCurve(to: CGPoint(x: 1.5597*width, y: -0.2977*height), control1: CGPoint(x: 1.55742*width, y: -0.32736*height), control2: CGPoint(x: 1.5597*width, y: -0.31465*height)) -// path.addCurve(to: CGPoint(x: 1.5318*width, y: -0.20659*height), control1: CGPoint(x: 1.5597*width, y: -0.25426*height), control2: CGPoint(x: 1.54585*width, y: -0.20976*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.61481*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.64643*width, y: -0.0678*height), control1: CGPoint(x: 1.63011*width, y: 0.01271*height), control2: CGPoint(x: 1.64189*width, y: -0.0339*height)) -// path.addCurve(to: CGPoint(x: 1.66958*width, y: 0.01271*height), control1: CGPoint(x: 1.64953*width, y: -0.01907*height), control2: CGPoint(x: 1.6578*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.7105*width, y: -0.19917*height), control1: CGPoint(x: 1.69273*width, y: 0.01271*height), control2: CGPoint(x: 1.70596*width, y: -0.11018*height)) -// path.addCurve(to: CGPoint(x: 1.70823*width, y: -0.22248*height), control1: CGPoint(x: 1.71112*width, y: -0.21082*height), control2: CGPoint(x: 1.71071*width, y: -0.21824*height)) -// path.addLine(to: CGPoint(x: 1.70017*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.69542*width, y: -0.22248*height), control1: CGPoint(x: 1.69769*width, y: -0.23943*height), control2: CGPoint(x: 1.69624*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.67599*width, y: -0.09429*height), control1: CGPoint(x: 1.69252*width, y: -0.18328*height), control2: CGPoint(x: 1.68549*width, y: -0.09429*height)) -// path.addCurve(to: CGPoint(x: 1.67123*width, y: -0.18434*height), control1: CGPoint(x: 1.6702*width, y: -0.09429*height), control2: CGPoint(x: 1.66958*width, y: -0.12819*height)) -// path.addLine(to: CGPoint(x: 1.67785*width, y: -0.40258*height)) -// path.addCurve(to: CGPoint(x: 1.67433*width, y: -0.42377*height), control1: CGPoint(x: 1.67826*width, y: -0.41423*height), control2: CGPoint(x: 1.67681*width, y: -0.42377*height)) -// path.addLine(to: CGPoint(x: 1.6578*width, y: -0.42377*height)) -// path.addCurve(to: CGPoint(x: 1.65305*width, y: -0.40258*height), control1: CGPoint(x: 1.65532*width, y: -0.42377*height), control2: CGPoint(x: 1.65346*width, y: -0.41529*height)) -// path.addLine(to: CGPoint(x: 1.65284*width, y: -0.39622*height)) -// path.addCurve(to: CGPoint(x: 1.63176*width, y: -0.43648*height), control1: CGPoint(x: 1.64953*width, y: -0.41105*height), control2: CGPoint(x: 1.64292*width, y: -0.43648*height)) -// path.addCurve(to: CGPoint(x: 1.5867*width, y: -0.24473*height), control1: CGPoint(x: 1.61171*width, y: -0.43648*height), control2: CGPoint(x: 1.59332*width, y: -0.35596*height)) -// path.addCurve(to: CGPoint(x: 1.61481*width, y: 0.01271*height), control1: CGPoint(x: 1.57926*width, y: -0.11865*height), control2: CGPoint(x: 1.58918*width, y: 0.01271*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.64974*width, y: -0.29346*height)) -// path.addCurve(to: CGPoint(x: 1.62308*width, y: -0.09429*height), control1: CGPoint(x: 1.64685*width, y: -0.18858*height), control2: CGPoint(x: 1.63589*width, y: -0.09429*height)) -// path.addCurve(to: CGPoint(x: 1.61212*width, y: -0.21294*height), control1: CGPoint(x: 1.61336*width, y: -0.09429*height), control2: CGPoint(x: 1.60902*width, y: -0.15044*height)) -// path.addCurve(to: CGPoint(x: 1.63651*width, y: -0.3263*height), control1: CGPoint(x: 1.61585*width, y: -0.28604*height), control2: CGPoint(x: 1.62639*width, y: -0.3263*height)) -// path.addCurve(to: CGPoint(x: 1.64974*width, y: -0.29346*height), control1: CGPoint(x: 1.64375*width, y: -0.3263*height), control2: CGPoint(x: 1.64767*width, y: -0.30511*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.68931*width, y: -0.02119*height)) -// path.addCurve(to: CGPoint(x: 1.69282*width, y: 0), control1: CGPoint(x: 1.6891*width, y: -0.00953*height), control2: CGPoint(x: 1.69055*width, y: 0)) -// path.addLine(to: CGPoint(x: 1.70935*width, y: 0)) -// path.addCurve(to: CGPoint(x: 1.71411*width, y: -0.02119*height), control1: CGPoint(x: 1.71225*width, y: 0), control2: CGPoint(x: 1.71369*width, y: -0.00848*height)) -// path.addLine(to: CGPoint(x: 1.71597*width, y: -0.08263*height)) -// path.addCurve(to: CGPoint(x: 1.74366*width, y: -0.31677*height), control1: CGPoint(x: 1.71886*width, y: -0.17798*height), control2: CGPoint(x: 1.73292*width, y: -0.31677*height)) -// path.addCurve(to: CGPoint(x: 1.748*width, y: -0.2532*height), control1: CGPoint(x: 1.74821*width, y: -0.31677*height), control2: CGPoint(x: 1.74924*width, y: -0.29346*height)) -// path.addLine(to: CGPoint(x: 1.7447*width, y: -0.14832*height)) -// path.addCurve(to: CGPoint(x: 1.76888*width, y: 0.01271*height), control1: CGPoint(x: 1.74201*width, y: -0.05933*height), control2: CGPoint(x: 1.7511*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.8098*width, y: -0.19917*height), control1: CGPoint(x: 1.79202*width, y: 0.01271*height), control2: CGPoint(x: 1.80525*width, y: -0.11018*height)) -// path.addCurve(to: CGPoint(x: 1.80753*width, y: -0.22248*height), control1: CGPoint(x: 1.81042*width, y: -0.21082*height), control2: CGPoint(x: 1.81001*width, y: -0.21824*height)) -// path.addLine(to: CGPoint(x: 1.79946*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.79471*width, y: -0.22248*height), control1: CGPoint(x: 1.79698*width, y: -0.23943*height), control2: CGPoint(x: 1.79574*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.77528*width, y: -0.09429*height), control1: CGPoint(x: 1.79161*width, y: -0.18328*height), control2: CGPoint(x: 1.78479*width, y: -0.09429*height)) -// path.addCurve(to: CGPoint(x: 1.77053*width, y: -0.18434*height), control1: CGPoint(x: 1.7695*width, y: -0.09429*height), control2: CGPoint(x: 1.76888*width, y: -0.12819*height)) -// path.addLine(to: CGPoint(x: 1.7728*width, y: -0.26274*height)) -// path.addCurve(to: CGPoint(x: 1.75358*width, y: -0.43648*height), control1: CGPoint(x: 1.7759*width, y: -0.35914*height), control2: CGPoint(x: 1.77032*width, y: -0.43648*height)) -// path.addCurve(to: CGPoint(x: 1.72403*width, y: -0.34749*height), control1: CGPoint(x: 1.74056*width, y: -0.43648*height), control2: CGPoint(x: 1.73044*width, y: -0.39092*height)) -// path.addLine(to: CGPoint(x: 1.72568*width, y: -0.40258*height)) -// path.addCurve(to: CGPoint(x: 1.72217*width, y: -0.42377*height), control1: CGPoint(x: 1.7261*width, y: -0.41423*height), control2: CGPoint(x: 1.72465*width, y: -0.42377*height)) -// path.addLine(to: CGPoint(x: 1.70584*width, y: -0.42377*height)) -// path.addCurve(to: CGPoint(x: 1.70109*width, y: -0.40258*height), control1: CGPoint(x: 1.70336*width, y: -0.42377*height), control2: CGPoint(x: 1.7015*width, y: -0.41529*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.87397*width, y: -0.214*height)) -// path.addCurve(to: CGPoint(x: 1.83883*width, y: -0.09641*height), control1: CGPoint(x: 1.86611*width, y: -0.13137*height), control2: CGPoint(x: 1.85144*width, y: -0.09641*height)) -// path.addCurve(to: CGPoint(x: 1.82147*width, y: -0.14302*height), control1: CGPoint(x: 1.82994*width, y: -0.09641*height), control2: CGPoint(x: 1.82436*width, y: -0.11442*height)) -// path.addCurve(to: CGPoint(x: 1.86901*width, y: -0.28604*height), control1: CGPoint(x: 1.83945*width, y: -0.1409*height), control2: CGPoint(x: 1.86425*width, y: -0.18434*height)) -// path.addCurve(to: CGPoint(x: 1.84172*width, y: -0.43648*height), control1: CGPoint(x: 1.87314*width, y: -0.37186*height), control2: CGPoint(x: 1.86032*width, y: -0.43648*height)) -// path.addCurve(to: CGPoint(x: 1.79336*width, y: -0.23095*height), control1: CGPoint(x: 1.82105*width, y: -0.43648*height), control2: CGPoint(x: 1.79832*width, y: -0.35702*height)) -// path.addCurve(to: CGPoint(x: 1.83428*width, y: 0.01271*height), control1: CGPoint(x: 1.78778*width, y: -0.09323*height), control2: CGPoint(x: 1.80721*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.88988*width, y: -0.19917*height), control1: CGPoint(x: 1.86012*width, y: 0.01271*height), control2: CGPoint(x: 1.88161*width, y: -0.08263*height)) -// path.addCurve(to: CGPoint(x: 1.8876*width, y: -0.22248*height), control1: CGPoint(x: 1.8907*width, y: -0.21082*height), control2: CGPoint(x: 1.89008*width, y: -0.21824*height)) -// path.addLine(to: CGPoint(x: 1.87954*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.87396*width, y: -0.214*height), control1: CGPoint(x: 1.87706*width, y: -0.23943*height), control2: CGPoint(x: 1.87603*width, y: -0.23519*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.81961*width, y: -0.20659*height)) -// path.addCurve(to: CGPoint(x: 1.81982*width, y: -0.214*height), control1: CGPoint(x: 1.81961*width, y: -0.20871*height), control2: CGPoint(x: 1.81982*width, y: -0.21188*height)) -// path.addCurve(to: CGPoint(x: 1.8411*width, y: -0.32736*height), control1: CGPoint(x: 1.82209*width, y: -0.27863*height), control2: CGPoint(x: 1.83325*width, y: -0.32736*height)) -// path.addCurve(to: CGPoint(x: 1.84751*width, y: -0.2977*height), control1: CGPoint(x: 1.84524*width, y: -0.32736*height), control2: CGPoint(x: 1.84751*width, y: -0.31465*height)) -// path.addCurve(to: CGPoint(x: 1.81961*width, y: -0.20659*height), control1: CGPoint(x: 1.84751*width, y: -0.25426*height), control2: CGPoint(x: 1.83366*width, y: -0.20976*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 1.95305*width, y: -0.22248*height)) -// path.addCurve(to: CGPoint(x: 1.93177*width, y: -0.09429*height), control1: CGPoint(x: 1.94995*width, y: -0.17904*height), control2: CGPoint(x: 1.94169*width, y: -0.09429*height)) -// path.addCurve(to: CGPoint(x: 1.92598*width, y: -0.16527*height), control1: CGPoint(x: 1.92701*width, y: -0.09429*height), control2: CGPoint(x: 1.92371*width, y: -0.11442*height)) -// path.addCurve(to: CGPoint(x: 1.93342*width, y: -0.31253*height), control1: CGPoint(x: 1.92867*width, y: -0.22777*height), control2: CGPoint(x: 1.93301*width, y: -0.30405*height)) -// path.addCurve(to: CGPoint(x: 1.92763*width, y: -0.38245*height), control1: CGPoint(x: 1.93487*width, y: -0.34749*height), control2: CGPoint(x: 1.93445*width, y: -0.36656*height)) -// path.addCurve(to: CGPoint(x: 1.8987*width, y: -0.44813*height), control1: CGPoint(x: 1.9173*width, y: -0.40576*height), control2: CGPoint(x: 1.90469*width, y: -0.42589*height)) -// path.addCurve(to: CGPoint(x: 1.88692*width, y: -0.52017*height), control1: CGPoint(x: 1.89911*width, y: -0.48945*height), control2: CGPoint(x: 1.89498*width, y: -0.52441*height)) -// path.addCurve(to: CGPoint(x: 1.87865*width, y: -0.40258*height), control1: CGPoint(x: 1.87762*width, y: -0.51594*height), control2: CGPoint(x: 1.87369*width, y: -0.46508*height)) -// path.addCurve(to: CGPoint(x: 1.87431*width, y: -0.21506*height), control1: CGPoint(x: 1.87989*width, y: -0.3655*height), control2: CGPoint(x: 1.87906*width, y: -0.27969*height)) -// path.addCurve(to: CGPoint(x: 1.87576*width, y: -0.19493*height), control1: CGPoint(x: 1.87348*width, y: -0.20553*height), control2: CGPoint(x: 1.8739*width, y: -0.19811*height)) -// path.addLine(to: CGPoint(x: 1.88547*width, y: -0.17586*height)) -// path.addCurve(to: CGPoint(x: 1.88919*width, y: -0.18434*height), control1: CGPoint(x: 1.88733*width, y: -0.17268*height), control2: CGPoint(x: 1.88857*width, y: -0.17374*height)) -// path.addCurve(to: CGPoint(x: 1.8958*width, y: -0.35279*height), control1: CGPoint(x: 1.89229*width, y: -0.23519*height), control2: CGPoint(x: 1.89456*width, y: -0.29346*height)) -// path.addLine(to: CGPoint(x: 1.90386*width, y: -0.33372*height)) -// path.addCurve(to: CGPoint(x: 1.90717*width, y: -0.29664*height), control1: CGPoint(x: 1.90717*width, y: -0.3263*height), control2: CGPoint(x: 1.9082*width, y: -0.31783*height)) -// path.addCurve(to: CGPoint(x: 1.89952*width, y: -0.14408*height), control1: CGPoint(x: 1.90448*width, y: -0.23625*height), control2: CGPoint(x: 1.90118*width, y: -0.19387*height)) -// path.addCurve(to: CGPoint(x: 1.9266*width, y: 0.01271*height), control1: CGPoint(x: 1.89704*width, y: -0.06145*height), control2: CGPoint(x: 1.90924*width, y: 0.01271*height)) -// path.addCurve(to: CGPoint(x: 1.96814*width, y: -0.19917*height), control1: CGPoint(x: 1.94913*width, y: 0.01271*height), control2: CGPoint(x: 1.96359*width, y: -0.11018*height)) -// path.addCurve(to: CGPoint(x: 1.96587*width, y: -0.22248*height), control1: CGPoint(x: 1.96876*width, y: -0.21082*height), control2: CGPoint(x: 1.96835*width, y: -0.21824*height)) -// path.addLine(to: CGPoint(x: 1.95781*width, y: -0.23519*height)) -// path.addCurve(to: CGPoint(x: 1.95305*width, y: -0.22248*height), control1: CGPoint(x: 1.95533*width, y: -0.23943*height), control2: CGPoint(x: 1.95388*width, y: -0.23519*height)) -// path.closeSubpath() -// return path -// } -//} -// -//// Ghost -//struct Ghost: Shape { -// func path(in rect: CGRect) -> Path { -// var path = Path() -// let width = rect.size.width -// let height = rect.size.height -// path.move(to: CGPoint(x: 0.47*width, y: 0.0815*height)) -// path.addCurve(to: CGPoint(x: 0.3148*width, y: 0.1708*height), control1: CGPoint(x: 0.4054*width, y: 0.0893*height), control2: CGPoint(x: 0.353*width, y: 0.1195*height)) -// path.addCurve(to: CGPoint(x: 0.2539*width, y: 0.3318*height), control1: CGPoint(x: 0.2841*width, y: 0.2119*height), control2: CGPoint(x: 0.2634*width, y: 0.2668*height)) -// path.addCurve(to: CGPoint(x: 0.2517*width, y: 0.4035*height), control1: CGPoint(x: 0.2515*width, y: 0.3483*height), control2: CGPoint(x: 0.2504*width, y: 0.3846*height)) -// path.addCurve(to: CGPoint(x: 0.2525*width, y: 0.4167*height), control1: CGPoint(x: 0.2522*width, y: 0.4106*height), control2: CGPoint(x: 0.2526*width, y: 0.4166*height)) -// path.addCurve(to: CGPoint(x: 0.2447*width, y: 0.4189*height), control1: CGPoint(x: 0.2524*width, y: 0.4169*height), control2: CGPoint(x: 0.2489*width, y: 0.4179*height)) -// path.addCurve(to: CGPoint(x: 0.1841*width, y: 0.4547*height), control1: CGPoint(x: 0.2245*width, y: 0.4241*height), control2: CGPoint(x: 0.2051*width, y: 0.4355*height)) -// path.addCurve(to: CGPoint(x: 0.15*width, y: 0.513*height), control1: CGPoint(x: 0.1646*width, y: 0.4723*height), control2: CGPoint(x: 0.15*width, y: 0.4974*height)) -// path.addCurve(to: CGPoint(x: 0.1569*width, y: 0.5309*height), control1: CGPoint(x: 0.15*width, y: 0.5195*height), control2: CGPoint(x: 0.1528*width, y: 0.5268*height)) -// path.addCurve(to: CGPoint(x: 0.2085*width, y: 0.5309*height), control1: CGPoint(x: 0.165*width, y: 0.539*height), control2: CGPoint(x: 0.1775*width, y: 0.539*height)) -// path.addCurve(to: CGPoint(x: 0.264*width, y: 0.5252*height), control1: CGPoint(x: 0.2538*width, y: 0.5191*height), control2: CGPoint(x: 0.2539*width, y: 0.5191*height)) -// path.addCurve(to: CGPoint(x: 0.272*width, y: 0.5335*height), control1: CGPoint(x: 0.2701*width, y: 0.529*height), control2: CGPoint(x: 0.2711*width, y: 0.53*height)) -// path.addCurve(to: CGPoint(x: 0.4178*width, y: 0.7853*height), control1: CGPoint(x: 0.3036*width, y: 0.6544*height), control2: CGPoint(x: 0.355*width, y: 0.7433*height)) -// path.addCurve(to: CGPoint(x: 0.5902*width, y: 0.7895*height), control1: CGPoint(x: 0.4685*width, y: 0.8193*height), control2: CGPoint(x: 0.5271*width, y: 0.8207*height)) -// path.addCurve(to: CGPoint(x: 0.6492*width, y: 0.7534*height), control1: CGPoint(x: 0.6081*width, y: 0.7807*height), control2: CGPoint(x: 0.6235*width, y: 0.7713*height)) -// path.addCurve(to: CGPoint(x: 0.7453*width, y: 0.709*height), control1: CGPoint(x: 0.7026*width, y: 0.7165*height), control2: CGPoint(x: 0.7188*width, y: 0.709*height)) -// path.addCurve(to: CGPoint(x: 0.8012*width, y: 0.7341*height), control1: CGPoint(x: 0.7664*width, y: 0.709*height), control2: CGPoint(x: 0.7842*width, y: 0.717*height)) -// path.addCurve(to: CGPoint(x: 0.816*width, y: 0.744*height), control1: CGPoint(x: 0.81*width, y: 0.7429*height), control2: CGPoint(x: 0.8116*width, y: 0.744*height)) -// path.addCurve(to: CGPoint(x: 0.8269*width, y: 0.7335*height), control1: CGPoint(x: 0.8201*width, y: 0.744*height), control2: CGPoint(x: 0.8254*width, y: 0.7389*height)) -// path.addCurve(to: CGPoint(x: 0.8265*width, y: 0.707*height), control1: CGPoint(x: 0.8284*width, y: 0.7278*height), control2: CGPoint(x: 0.8283*width, y: 0.7154*height)) -// path.addCurve(to: CGPoint(x: 0.7937*width, y: 0.6544*height), control1: CGPoint(x: 0.8223*width, y: 0.687*height), control2: CGPoint(x: 0.8115*width, y: 0.6696*height)) -// path.addCurve(to: CGPoint(x: 0.7576*width, y: 0.6304*height), control1: CGPoint(x: 0.7808*width, y: 0.6433*height), control2: CGPoint(x: 0.7711*width, y: 0.6369*height)) -// path.addCurve(to: CGPoint(x: 0.6647*width, y: 0.6285*height), control1: CGPoint(x: 0.725*width, y: 0.6149*height), control2: CGPoint(x: 0.6977*width, y: 0.6143*height)) -// path.addCurve(to: CGPoint(x: 0.6694*width, y: 0.6162*height), control1: CGPoint(x: 0.6633*width, y: 0.6291*height), control2: CGPoint(x: 0.6646*width, y: 0.6257*height)) -// path.addCurve(to: CGPoint(x: 0.7549*width, y: 0.3918*height), control1: CGPoint(x: 0.7139*width, y: 0.5273*height), control2: CGPoint(x: 0.7443*width, y: 0.4478*height)) -// path.addCurve(to: CGPoint(x: 0.759*width, y: 0.31*height), control1: CGPoint(x: 0.76*width, y: 0.3651*height), control2: CGPoint(x: 0.7616*width, y: 0.3337*height)) -// path.addCurve(to: CGPoint(x: 0.6846*width, y: 0.1554*height), control1: CGPoint(x: 0.7528*width, y: 0.252*height), control2: CGPoint(x: 0.7264*width, y: 0.1972*height)) -// path.addCurve(to: CGPoint(x: 0.5329*width, y: 0.0815*height), control1: CGPoint(x: 0.6431*width, y: 0.114*height), control2: CGPoint(x: 0.5913*width, y: 0.0888*height)) -// path.addCurve(to: CGPoint(x: 0.47*width, y: 0.0815*height), control1: CGPoint(x: 0.518*width, y: 0.0797*height), control2: CGPoint(x: 0.4851*width, y: 0.0797*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 0.534*width, y: 0.0985*height)) -// path.addCurve(to: CGPoint(x: 0.6525*width, y: 0.1488*height), control1: CGPoint(x: 0.577*width, y: 0.1042*height), control2: CGPoint(x: 0.6175*width, y: 0.1214*height)) -// path.addCurve(to: CGPoint(x: 0.6948*width, y: 0.1922*height), control1: CGPoint(x: 0.663*width, y: 0.157*height), control2: CGPoint(x: 0.6865*width, y: 0.1812*height)) -// path.addCurve(to: CGPoint(x: 0.741*width, y: 0.374*height), control1: CGPoint(x: 0.7344*width, y: 0.245*height), control2: CGPoint(x: 0.7506*width, y: 0.3087*height)) -// path.addCurve(to: CGPoint(x: 0.6504*width, y: 0.6166*height), control1: CGPoint(x: 0.732*width, y: 0.435*height), control2: CGPoint(x: 0.7061*width, y: 0.5045*height)) -// path.addCurve(to: CGPoint(x: 0.6311*width, y: 0.6609*height), control1: CGPoint(x: 0.6311*width, y: 0.6556*height), control2: CGPoint(x: 0.6301*width, y: 0.6579*height)) -// path.addCurve(to: CGPoint(x: 0.6403*width, y: 0.6664*height), control1: CGPoint(x: 0.6324*width, y: 0.6646*height), control2: CGPoint(x: 0.6371*width, y: 0.6674*height)) -// path.addCurve(to: CGPoint(x: 0.6505*width, y: 0.6588*height), control1: CGPoint(x: 0.6415*width, y: 0.666*height), control2: CGPoint(x: 0.6461*width, y: 0.6626*height)) -// path.addCurve(to: CGPoint(x: 0.704*width, y: 0.6353*height), control1: CGPoint(x: 0.6679*width, y: 0.6436*height), control2: CGPoint(x: 0.685*width, y: 0.6362*height)) -// path.addCurve(to: CGPoint(x: 0.7833*width, y: 0.6673*height), control1: CGPoint(x: 0.7305*width, y: 0.634*height), control2: CGPoint(x: 0.757*width, y: 0.6447*height)) -// path.addCurve(to: CGPoint(x: 0.8108*width, y: 0.7143*height), control1: CGPoint(x: 0.7983*width, y: 0.6803*height), control2: CGPoint(x: 0.8081*width, y: 0.6969*height)) -// path.addCurve(to: CGPoint(x: 0.8101*width, y: 0.719*height), control1: CGPoint(x: 0.8116*width, y: 0.7193*height), control2: CGPoint(x: 0.8115*width, y: 0.7198*height)) -// path.addCurve(to: CGPoint(x: 0.8065*width, y: 0.716*height), control1: CGPoint(x: 0.8092*width, y: 0.7186*height), control2: CGPoint(x: 0.8076*width, y: 0.7172*height)) -// path.addCurve(to: CGPoint(x: 0.7675*width, y: 0.6948*height), control1: CGPoint(x: 0.8*width, y: 0.7088*height), control2: CGPoint(x: 0.7812*width, y: 0.6986*height)) -// path.addCurve(to: CGPoint(x: 0.7209*width, y: 0.6953*height), control1: CGPoint(x: 0.7554*width, y: 0.6916*height), control2: CGPoint(x: 0.7341*width, y: 0.6918*height)) -// path.addCurve(to: CGPoint(x: 0.6629*width, y: 0.724*height), control1: CGPoint(x: 0.7013*width, y: 0.7005*height), control2: CGPoint(x: 0.6868*width, y: 0.7076*height)) -// path.addCurve(to: CGPoint(x: 0.5548*width, y: 0.7864*height), control1: CGPoint(x: 0.5998*width, y: 0.7672*height), control2: CGPoint(x: 0.583*width, y: 0.7769*height)) -// path.addCurve(to: CGPoint(x: 0.508*width, y: 0.7947*height), control1: CGPoint(x: 0.5376*width, y: 0.7921*height), control2: CGPoint(x: 0.5265*width, y: 0.7941*height)) -// path.addCurve(to: CGPoint(x: 0.4626*width, y: 0.7888*height), control1: CGPoint(x: 0.489*width, y: 0.7953*height), control2: CGPoint(x: 0.4788*width, y: 0.794*height)) -// path.addCurve(to: CGPoint(x: 0.3776*width, y: 0.725*height), control1: CGPoint(x: 0.4334*width, y: 0.7796*height), control2: CGPoint(x: 0.4033*width, y: 0.757*height)) -// path.addCurve(to: CGPoint(x: 0.3451*width, y: 0.6763*height), control1: CGPoint(x: 0.3696*width, y: 0.715*height), control2: CGPoint(x: 0.3525*width, y: 0.6894*height)) -// path.addCurve(to: CGPoint(x: 0.2886*width, y: 0.5293*height), control1: CGPoint(x: 0.3234*width, y: 0.638*height), control2: CGPoint(x: 0.3017*width, y: 0.5813*height)) -// path.addCurve(to: CGPoint(x: 0.2683*width, y: 0.3864*height), control1: CGPoint(x: 0.2758*width, y: 0.4785*height), control2: CGPoint(x: 0.2692*width, y: 0.4321*height)) -// path.addCurve(to: CGPoint(x: 0.2726*width, y: 0.3205*height), control1: CGPoint(x: 0.2677*width, y: 0.3559*height), control2: CGPoint(x: 0.2684*width, y: 0.3444*height)) -// path.addCurve(to: CGPoint(x: 0.4818*width, y: 0.097*height), control1: CGPoint(x: 0.2961*width, y: 0.1862*height), control2: CGPoint(x: 0.3708*width, y: 0.1063*height)) -// path.addCurve(to: CGPoint(x: 0.534*width, y: 0.0985*height), control1: CGPoint(x: 0.4951*width, y: 0.0959*height), control2: CGPoint(x: 0.5201*width, y: 0.0966*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 0.2544*width, y: 0.4372*height)) -// path.addCurve(to: CGPoint(x: 0.2575*width, y: 0.4589*height), control1: CGPoint(x: 0.2547*width, y: 0.439*height), control2: CGPoint(x: 0.2561*width, y: 0.4488*height)) -// path.addCurve(to: CGPoint(x: 0.2656*width, y: 0.5051*height), control1: CGPoint(x: 0.2597*width, y: 0.4749*height), control2: CGPoint(x: 0.2642*width, y: 0.5003*height)) -// path.addCurve(to: CGPoint(x: 0.2618*width, y: 0.5053*height), control1: CGPoint(x: 0.266*width, y: 0.5067*height), control2: CGPoint(x: 0.2657*width, y: 0.5067*height)) -// path.addCurve(to: CGPoint(x: 0.2233*width, y: 0.5101*height), control1: CGPoint(x: 0.2554*width, y: 0.503*height), control2: CGPoint(x: 0.246*width, y: 0.5042*height)) -// path.addCurve(to: CGPoint(x: 0.1775*width, y: 0.5205*height), control1: CGPoint(x: 0.1947*width, y: 0.5175*height), control2: CGPoint(x: 0.1846*width, y: 0.5198*height)) -// path.addCurve(to: CGPoint(x: 0.169*width, y: 0.519*height), control1: CGPoint(x: 0.1718*width, y: 0.5211*height), control2: CGPoint(x: 0.171*width, y: 0.521*height)) -// path.addCurve(to: CGPoint(x: 0.1791*width, y: 0.4849*height), control1: CGPoint(x: 0.1641*width, y: 0.5141*height), control2: CGPoint(x: 0.1686*width, y: 0.499*height)) -// path.addCurve(to: CGPoint(x: 0.2285*width, y: 0.4427*height), control1: CGPoint(x: 0.1894*width, y: 0.4709*height), control2: CGPoint(x: 0.2135*width, y: 0.4504*height)) -// path.addCurve(to: CGPoint(x: 0.2527*width, y: 0.434*height), control1: CGPoint(x: 0.2342*width, y: 0.4398*height), control2: CGPoint(x: 0.2501*width, y: 0.4341*height)) -// path.addCurve(to: CGPoint(x: 0.2544*width, y: 0.4372*height), control1: CGPoint(x: 0.2534*width, y: 0.434*height), control2: CGPoint(x: 0.2541*width, y: 0.4355*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 0.4813*width, y: 0.2149*height)) -// path.addCurve(to: CGPoint(x: 0.4432*width, y: 0.2634*height), control1: CGPoint(x: 0.4651*width, y: 0.2208*height), control2: CGPoint(x: 0.4513*width, y: 0.2383*height)) -// path.addCurve(to: CGPoint(x: 0.4356*width, y: 0.312*height), control1: CGPoint(x: 0.4376*width, y: 0.2805*height), control2: CGPoint(x: 0.4356*width, y: 0.2933*height)) -// path.addCurve(to: CGPoint(x: 0.4404*width, y: 0.3387*height), control1: CGPoint(x: 0.4356*width, y: 0.3288*height), control2: CGPoint(x: 0.4366*width, y: 0.3342*height)) -// path.addCurve(to: CGPoint(x: 0.4655*width, y: 0.3364*height), control1: CGPoint(x: 0.4439*width, y: 0.3427*height), control2: CGPoint(x: 0.4488*width, y: 0.3423*height)) -// path.addCurve(to: CGPoint(x: 0.492*width, y: 0.3315*height), control1: CGPoint(x: 0.4793*width, y: 0.3316*height), control2: CGPoint(x: 0.4796*width, y: 0.3316*height)) -// path.addCurve(to: CGPoint(x: 0.5195*width, y: 0.3324*height), control1: CGPoint(x: 0.4989*width, y: 0.3315*height), control2: CGPoint(x: 0.5113*width, y: 0.3319*height)) -// path.addCurve(to: CGPoint(x: 0.5466*width, y: 0.3276*height), control1: CGPoint(x: 0.5369*width, y: 0.3334*height), control2: CGPoint(x: 0.5416*width, y: 0.3326*height)) -// path.addCurve(to: CGPoint(x: 0.552*width, y: 0.2905*height), control1: CGPoint(x: 0.5526*width, y: 0.3216*height), control2: CGPoint(x: 0.5542*width, y: 0.3104*height)) -// path.addCurve(to: CGPoint(x: 0.516*width, y: 0.22*height), control1: CGPoint(x: 0.5484*width, y: 0.2589*height), control2: CGPoint(x: 0.5346*width, y: 0.2319*height)) -// path.addCurve(to: CGPoint(x: 0.4813*width, y: 0.2149*height), control1: CGPoint(x: 0.5055*width, y: 0.2133*height), control2: CGPoint(x: 0.4916*width, y: 0.2112*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 0.3452*width, y: 0.2165*height)) -// path.addCurve(to: CGPoint(x: 0.306*width, y: 0.2848*height), control1: CGPoint(x: 0.3275*width, y: 0.2247*height), control2: CGPoint(x: 0.3104*width, y: 0.2544*height)) -// path.addCurve(to: CGPoint(x: 0.3052*width, y: 0.3052*height), control1: CGPoint(x: 0.3053*width, y: 0.2892*height), control2: CGPoint(x: 0.305*width, y: 0.2984*height)) -// path.addCurve(to: CGPoint(x: 0.3129*width, y: 0.3277*height), control1: CGPoint(x: 0.3056*width, y: 0.3193*height), control2: CGPoint(x: 0.3072*width, y: 0.3239*height)) -// path.addCurve(to: CGPoint(x: 0.3367*width, y: 0.3279*height), control1: CGPoint(x: 0.3171*width, y: 0.3306*height), control2: CGPoint(x: 0.3204*width, y: 0.3306*height)) -// path.addCurve(to: CGPoint(x: 0.3708*width, y: 0.3305*height), control1: CGPoint(x: 0.3529*width, y: 0.3252*height), control2: CGPoint(x: 0.3592*width, y: 0.3257*height)) -// path.addCurve(to: CGPoint(x: 0.3942*width, y: 0.3371*height), control1: CGPoint(x: 0.3862*width, y: 0.3369*height), control2: CGPoint(x: 0.3912*width, y: 0.3383*height)) -// path.addCurve(to: CGPoint(x: 0.3985*width, y: 0.3331*height), control1: CGPoint(x: 0.3957*width, y: 0.3366*height), control2: CGPoint(x: 0.3976*width, y: 0.3347*height)) -// path.addCurve(to: CGPoint(x: 0.3964*width, y: 0.2647*height), control1: CGPoint(x: 0.4036*width, y: 0.3231*height), control2: CGPoint(x: 0.4026*width, y: 0.2895*height)) -// path.addCurve(to: CGPoint(x: 0.3657*width, y: 0.2155*height), control1: CGPoint(x: 0.39*width, y: 0.2389*height), control2: CGPoint(x: 0.3788*width, y: 0.221*height)) -// path.addCurve(to: CGPoint(x: 0.3452*width, y: 0.2165*height), control1: CGPoint(x: 0.3601*width, y: 0.2132*height), control2: CGPoint(x: 0.3514*width, y: 0.2136*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 0.4847*width, y: 0.4141*height)) -// path.addCurve(to: CGPoint(x: 0.4028*width, y: 0.4568*height), control1: CGPoint(x: 0.4575*width, y: 0.417*height), control2: CGPoint(x: 0.4287*width, y: 0.432*height)) -// path.addCurve(to: CGPoint(x: 0.372*width, y: 0.5039*height), control1: CGPoint(x: 0.3864*width, y: 0.4726*height), control2: CGPoint(x: 0.3753*width, y: 0.4895*height)) -// path.addCurve(to: CGPoint(x: 0.3808*width, y: 0.5335*height), control1: CGPoint(x: 0.3692*width, y: 0.5161*height), control2: CGPoint(x: 0.3726*width, y: 0.5277*height)) -// path.addCurve(to: CGPoint(x: 0.4297*width, y: 0.5298*height), control1: CGPoint(x: 0.389*width, y: 0.5392*height), control2: CGPoint(x: 0.4035*width, y: 0.5381*height)) -// path.addCurve(to: CGPoint(x: 0.463*width, y: 0.521*height), control1: CGPoint(x: 0.4497*width, y: 0.5235*height), control2: CGPoint(x: 0.4592*width, y: 0.521*height)) -// path.addCurve(to: CGPoint(x: 0.4835*width, y: 0.5305*height), control1: CGPoint(x: 0.4655*width, y: 0.521*height), control2: CGPoint(x: 0.4712*width, y: 0.5237*height)) -// path.addLine(to: CGPoint(x: 0.5004*width, y: 0.5399*height)) -// path.addLine(to: CGPoint(x: 0.5042*width, y: 0.5332*height)) -// path.addCurve(to: CGPoint(x: 0.508*width, y: 0.5258*height), control1: CGPoint(x: 0.5063*width, y: 0.5295*height), control2: CGPoint(x: 0.508*width, y: 0.5262*height)) -// path.addCurve(to: CGPoint(x: 0.4681*width, y: 0.5044*height), control1: CGPoint(x: 0.508*width, y: 0.5246*height), control2: CGPoint(x: 0.4716*width, y: 0.505*height)) -// path.addCurve(to: CGPoint(x: 0.4329*width, y: 0.5114*height), control1: CGPoint(x: 0.4626*width, y: 0.5033*height), control2: CGPoint(x: 0.4517*width, y: 0.5055*height)) -// path.addCurve(to: CGPoint(x: 0.3905*width, y: 0.5196*height), control1: CGPoint(x: 0.4037*width, y: 0.5206*height), control2: CGPoint(x: 0.3946*width, y: 0.5223*height)) -// path.addCurve(to: CGPoint(x: 0.388*width, y: 0.5122*height), control1: CGPoint(x: 0.3883*width, y: 0.5182*height), control2: CGPoint(x: 0.388*width, y: 0.5172*height)) -// path.addCurve(to: CGPoint(x: 0.4404*width, y: 0.448*height), control1: CGPoint(x: 0.3881*width, y: 0.4946*height), control2: CGPoint(x: 0.4088*width, y: 0.4693*height)) -// path.addCurve(to: CGPoint(x: 0.4875*width, y: 0.4309*height), control1: CGPoint(x: 0.4554*width, y: 0.438*height), control2: CGPoint(x: 0.4691*width, y: 0.433*height)) -// path.addCurve(to: CGPoint(x: 0.5648*width, y: 0.4481*height), control1: CGPoint(x: 0.5106*width, y: 0.4283*height), control2: CGPoint(x: 0.5396*width, y: 0.4347*height)) -// path.addCurve(to: CGPoint(x: 0.5766*width, y: 0.453*height), control1: CGPoint(x: 0.5699*width, y: 0.4508*height), control2: CGPoint(x: 0.5752*width, y: 0.453*height)) -// path.addCurve(to: CGPoint(x: 0.584*width, y: 0.445*height), control1: CGPoint(x: 0.5801*width, y: 0.453*height), control2: CGPoint(x: 0.584*width, y: 0.4488*height)) -// path.addCurve(to: CGPoint(x: 0.5658*width, y: 0.43*height), control1: CGPoint(x: 0.584*width, y: 0.4402*height), control2: CGPoint(x: 0.5797*width, y: 0.4367*height)) -// path.addCurve(to: CGPoint(x: 0.5108*width, y: 0.414*height), control1: CGPoint(x: 0.5463*width, y: 0.4206*height), control2: CGPoint(x: 0.5309*width, y: 0.4161*height)) -// path.addCurve(to: CGPoint(x: 0.4847*width, y: 0.4141*height), control1: CGPoint(x: 0.5003*width, y: 0.4129*height), control2: CGPoint(x: 0.4958*width, y: 0.4129*height)) -// path.closeSubpath() -// path.move(to: CGPoint(x: 0.4665*width, y: 0.893*height)) -// path.addCurve(to: CGPoint(x: 0.3071*width, y: 0.9064*height), control1: CGPoint(x: 0.3762*width, y: 0.8947*height), control2: CGPoint(x: 0.3136*width, y: 0.8999*height)) -// path.addCurve(to: CGPoint(x: 0.3093*width, y: 0.9095*height), control1: CGPoint(x: 0.3058*width, y: 0.9078*height), control2: CGPoint(x: 0.306*width, y: 0.9081*height)) -// path.addCurve(to: CGPoint(x: 0.6915*width, y: 0.9164*height), control1: CGPoint(x: 0.3396*width, y: 0.9221*height), control2: CGPoint(x: 0.5731*width, y: 0.9264*height)) -// path.addCurve(to: CGPoint(x: 0.7394*width, y: 0.9075*height), control1: CGPoint(x: 0.7204*width, y: 0.914*height), control2: CGPoint(x: 0.7389*width, y: 0.9105*height)) -// path.addCurve(to: CGPoint(x: 0.4665*width, y: 0.893*height), control1: CGPoint(x: 0.7408*width, y: 0.8978*height), control2: CGPoint(x: 0.604*width, y: 0.8905*height)) -// path.closeSubpath() -// return path -// } -//} -// -//// Aurora -//struct AuroraView: View { -// -// private enum AnimationProperties { -// static let animationSpeed: Double = 4 -// static let timerDuration: TimeInterval = 3 -// static let blurRadius: CGFloat = 130 -// } -// -// @State private var timer = Timer.publish(every: AnimationProperties.timerDuration, on: .main, in: .common).autoconnect() -// @ObservedObject private var animator = CircleAnimator(colors: AuroraColors.all) -// -// var body: some View { -// ZStack { -// ZStack { -// ForEach(animator.circles) { circle in -// MovingCircle(originOffset: circle.position) -// .foregroundColor(circle.color) -// } -// }.blur(radius: AnimationProperties.blurRadius) -// } -// .onDisappear { -// timer.upstream.connect().cancel() -// } -// .onAppear { -// animateCircles() -// timer = Timer.publish(every: AnimationProperties.timerDuration, on: .main, in: .common).autoconnect() -// } -// .onReceive(timer) { _ in -// animateCircles() -// } -// } -// -// private func animateCircles() { -// withAnimation(.easeInOut(duration: AnimationProperties.animationSpeed)) { -// animator.animate() -// } -// } -// -//} -// -//private enum AuroraColors { -// static var all: [Color] { -// [ -// // Color(red: 0/255, green: 0/255, blue: 128/255), // Dark Blue -// Color.purple, // Purple -// Color.pink, // Pink -// Color.orange, // Orange -// ] -// } -//} -// -//private struct MovingCircle: Shape { -// -// var originOffset: CGPoint -// -// var animatableData: CGPoint.AnimatableData { -// get { -// originOffset.animatableData -// } -// set { -// originOffset.animatableData = newValue -// } -// } -// -// func path(in rect: CGRect) -> Path { -// var path = Path() -// -// let adjustedX = rect.width * originOffset.x -// let adjustedY = rect.height * originOffset.y -// let smallestDimension = min(rect.width, rect.height) -// path.addArc(center: CGPoint(x: adjustedX, y: adjustedY), radius: smallestDimension/2, startAngle: .zero, endAngle: .degrees(360), clockwise: true) -// return path -// } -//} -// -//private class CircleAnimator: ObservableObject { -// class Circle: Identifiable { -// internal init(position: CGPoint, color: Color) { -// self.position = position -// self.color = color -// } -// var position: CGPoint -// let id = UUID().uuidString -// let color: Color -// } -// -// @Published private(set) var circles: [Circle] = [] -// -// -// init(colors: [Color]) { -// circles = colors.map({ color in -// Circle(position: CircleAnimator.generateRandomPosition(), color: color) -// }) -// } -// -// func animate() { -// objectWillChange.send() -// for circle in circles { -// circle.position = CircleAnimator.generateRandomPosition() -// } -// } -// -// static func generateRandomPosition() -> CGPoint { -// CGPoint(x: CGFloat.random(in: 0 ... 1), y: CGFloat.random(in: 0 ... 1)) -// } -//} -// -//// Ghost View -//// ZStack { -//// LinearGradient(colors: [.pink, .orange], startPoint: .topLeading, endPoint: .bottomTrailing) -//// .edgesIgnoringSafeArea(.all) -//// .hueRotation(.degrees(animateGradient ? 45 : 0)) -//// .onAppear { -//// withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { -//// animateGradient.toggle() -//// } -//// } -//// .mask( -//// Ghost() -//// .frame(width: 200, height: 200) -//// ) -//// } -// -//// Gradient Background -////ZStack { -//// LinearGradient(colors: [.pink, .orange], startPoint: .topLeading, endPoint: .bottomTrailing) -//// .edgesIgnoringSafeArea(.all) -//// .hueRotation(.degrees(animateGradient ? 45 : 0)) -//// .onAppear { -//// withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { -//// animateGradient.toggle() -//// } -//// } -//// .mask( -//// Ghost() -//// .frame(width: 200, height: 200) -//// ) -////} diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index abcd821..042c759 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -70,6 +70,46 @@ struct NavButtonBottomBarStyle: ButtonStyle { } + +struct InfoButton: View { + @State private var isPopoverPresented: Bool = false + let text: String + let color: Color? + + var body: some View { + Button(action: { + self.isPopoverPresented.toggle() + }) { + Image(systemName: "info.circle.fill") + .resizable() + .scaledToFit() + .frame(width: 14, height: 14) + .foregroundColor(color?.opacity(0.7) ?? Color("mode").opacity(0.7)) + } + .buttonStyle(PlainButtonStyle()) + .onHover { isHovered in + if isHovered { + NSCursor.pointingHand.push() + } else { + NSCursor.pop() + } + } + .popover(isPresented: $isPopoverPresented, arrowEdge: .bottom) { + VStack { + Spacer() + Text(text) + .font(.callout) + .frame(maxWidth: .infinity) + .padding() + Spacer() + } + .frame(width: 300) + } + .padding(.horizontal, 5) + } +} + + struct WarningPopoverView: View { var label: String var bodyText: String @@ -82,10 +122,14 @@ struct WarningPopoverView: View { .foregroundStyle(.red) .popover(isPresented: $isPresented, arrowEdge: .top) { VStack { + Spacer() Text(bodyText) + .font(.callout) + .frame(maxWidth: .infinity) .padding() - .font(.title2) + Spacer() } + .frame(width: 300) } Text(label) @@ -100,6 +144,121 @@ struct WarningPopoverView: View { } +public struct NewFeatureView: View { + var text: String + var mini: Bool + @Binding var showFeature: Bool + + public var body: some View { + VStack { + HStack { + Text("New features for v\((Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)!)!") + .font(.headline).bold() + Spacer() + Button("") { + withAnimation(Animation.easeInOut(duration: 0.5)) { + showFeature = false + } + } + .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close", color: .gray)) + .onHover { isHovered in + if isHovered { + NSCursor.pointingHand.push() + } else { + NSCursor.pop() + } + } + } + .padding() + + ScrollView() { + Text(text) + .font(.callout) + .multilineTextAlignment(.leading) + .padding(.trailing) + } + .padding(.horizontal) + .padding(.bottom) + + } + .frame(maxWidth: mini ? 200 : 400, maxHeight: mini ? 300 : 250) + .background(.ultraThinMaterial) + .cornerRadius(10) + .overlay( + RoundedRectangle(cornerRadius: 10) + .strokeBorder(Color("mode").opacity(0.2), lineWidth: 0.6) + ) + .padding() + } +} + + +public struct SlideableDivider: View { + @Binding var dimension: Double + @State private var dimensionStart: Double? + @State private var handleWidth: Double = 2 + @State private var handleHeight: Double = 30 + public init(dimension: Binding) { + self._dimension = dimension + } + + public var body: some View { + Rectangle() + .foregroundStyle(Color.gray.opacity(0.3)) + .frame(width: 1) + .overlay( + VStack(spacing: 0) { + Spacer() + RoundedRectangle(cornerRadius: 50) + .fill(Color("mode").opacity(0.3)) + .frame(width: handleWidth, height: handleHeight) + .onHover { inside in + if inside { + NSCursor.resizeLeftRight.push() + + } else { + NSCursor.pop() + + } + } + .contextMenu { + Button("Reset Size") { + dimension = 280 + } + } + .gesture(drag) + Spacer() + } + .offset(x: 5) + ) + } + + var drag: some Gesture { + DragGesture(minimumDistance: 5, coordinateSpace: CoordinateSpace.global) + .onChanged { val in + if dimensionStart == nil { + dimensionStart = dimension + } + let delta = val.location.x - val.startLocation.x + let newDimension = dimensionStart! + Double(delta) + + // Set minimum and maximum width + let minWidth: Double = 250 + let maxWidth: Double = 350 + dimension = max(minWidth, min(maxWidth, newDimension)) + NSCursor.closedHand.set() + handleWidth = 4 + handleHeight = 40 + } + .onEnded { val in + dimensionStart = nil + NSCursor.arrow.set() + handleWidth = 2 + handleHeight = 30 + } + } +} + struct LabeledDivider: View { let label: String @@ -140,7 +299,6 @@ struct AnimatedSearchStyle: TextFieldStyle { if isHovered || !text.isEmpty { configuration -// .frame(minWidth: 130) .frame(height: 15) .font(.title3) .textFieldStyle(PlainTextFieldStyle()) @@ -242,30 +400,9 @@ struct SimpleSearchStyle: TextFieldStyle { } configuration -// .frame(minWidth: mini ? 80 : 300) -// .frame(maxWidth: mini ? 150 : 300) .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() -// } -// } -// -// } + if trash && text != "" { Image(systemName: "xmark") @@ -472,42 +609,6 @@ private let redBG: AnyShapeStyle = AnyShapeStyle( -// Picker -//struct PillPicker: View { -// @Binding var index: Int -// var onTapAction: () -> Void -// -// var body: some View { -// -// HStack(alignment: .center, spacing: 20) { -// ForEach(0..<3) { i in -// HStack(spacing: 4) { -// Image(systemName: i == 0 ? "appclip" : "ellipsis.circle") -// .resizable() -// .scaledToFit() -// .frame(width: 16) -// .foregroundColor(index == i ? .black : .gray) -// -// Text(i == 0 ? "Apps" : (i == 1 ? "Widgets" : "Plugins")) -// .font(.system(size: 12)) -// .foregroundColor(index == i ? .black : .gray) -// } -// .padding(8) -// .background((Color.white).opacity(index == i ? 1 : 0)) -// .clipShape(Capsule()) -// .onTapGesture { -// withAnimation { -// index = i -// onTapAction() -// } -// } -// } -// } -// .background(Color.black.opacity(0.06)) -// .clipShape(Capsule()) -// } -//} - struct PillPicker: View { @Binding var index: Int var onTapAction: () -> Void @@ -526,12 +627,6 @@ struct PillPicker: View { .shadow(color: Color.black.opacity(0.2), radius: 2, x: 0, y: 2) HStack(spacing: 0) { - // Uncomment below for icon next to labels -// Image(systemName: i == 0 ? "appclip" : (i == 1 ? "macwindow" : "cube.box.fill")) -// .resizable() -// .scaledToFit() -// .frame(width: 16) -// .foregroundColor(index == i ? .black : .gray) Text(i == 0 ? "Apps" : (i == 1 ? "Widgets" : "Plugins")) .font(.system(size: 12)) @@ -561,7 +656,6 @@ struct PillPicker: View { .offset(y: 1) ) ) -// .background(Color.black.opacity(0.06)) .clipShape(Capsule()) } } @@ -574,11 +668,11 @@ struct PearDropView: View { var body: some View { VStack(alignment: .center, spacing: 0) { HStack(spacing: 0) { - LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing) + LinearGradient(gradient: Gradient(colors: [.green, .orange]), startPoint: .leading, endPoint: .trailing) .frame(width: 195) - LinearGradient(gradient: Gradient(colors: [.orange, .gray]), startPoint: .leading, endPoint: .trailing) + LinearGradient(gradient: Gradient(colors: [.orange, Color("mode").opacity(0.5)]), startPoint: .leading, endPoint: .trailing) .frame(width: 10) - LinearGradient(gradient: Gradient(colors: [.gray, .gray]), startPoint: .leading, endPoint: .trailing) + LinearGradient(gradient: Gradient(colors: [Color("mode").opacity(0.5), Color("mode").opacity(0.5)]), startPoint: .leading, endPoint: .trailing) .frame(width: 300) } .mask( @@ -588,90 +682,10 @@ struct PearDropView: View { .frame(width: 500, height: 120) .padding() ) -// LinearGradient(gradient: Gradient(colors: [.gray, .gray]), startPoint: .leading, endPoint: .trailing) -// .mask( -// Image("logo_text_small") -// .resizable() -// .scaledToFit() -// .frame(minWidth: 300, maxWidth: 350) -// .frame(height: 100) -// .padding() -// ) -// LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing) -// .mask( -// Image(systemName: "plus.square.dashed") -// .resizable() -// .scaledToFit() -// .frame(width: 50, height: 50) -// .padding() -// ) } .frame(height: 120) } } - - - - -// .frame(height: 50) - -// HStack(spacing: 10) { -// HStack(spacing: 2) { -// Image(systemName: "appclip") -// .foregroundColor(index == 0 ? .black : .gray) -// Text("Apps") -// .font(.footnote) -// .foregroundColor(index == 0 ? .black : .gray) -// } -// .padding(8) -// .background((Color.white).opacity(index == 0 ? 1 : 0)) -// .clipShape(Capsule()) -// .onTapGesture { -// withAnimation { -// index = 0 -// onTapAction() -// } -// } -// -// HStack(spacing: 2) { -// Image(systemName: "ellipsis.circle") -// .foregroundColor(index == 1 ? .black : .gray) -// Text("Widgets") -// .foregroundColor(index == 1 ? .black : .gray) -// -// -// } -// .padding(8) -// .background((Color.white).opacity(index == 1 ? 1 : 0)) -// .clipShape(Capsule()) -// .onTapGesture { -// withAnimation { -// index = 1 -// onTapAction() -// } -// } -// -// HStack(spacing: 2) { -// Image(systemName: "ellipsis.circle") -// .foregroundColor(index == 2 ? .black : .gray) -// Text("Plugins") -// .foregroundColor(index == 2 ? .black : .gray) -// -// -// } -// .padding(8) -// .background((Color.white).opacity(index == 2 ? 2 : 0)) -// .clipShape(Capsule()) -// .onTapGesture { -// withAnimation { -// index = 2 -// onTapAction() -// } -// } -// } -// .background(Color.black.opacity(0.06)) -// .clipShape(Capsule()) -// .frame(height: 50) diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index 4de17bf..1c45e1c 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -53,7 +53,6 @@ func checkFullDiskAccessForApp() -> Bool { process.standardError = pipeErr process.launch() -// let data = pipe.fileHandleForReading.readDataToEndOfFile() let dataErr = pipeErr.fileHandleForReading.readDataToEndOfFile() let output = String(data: dataErr, encoding: .utf8) @@ -86,7 +85,6 @@ func checkAndRequestFullDiskAccess(appState: AppState, skipAlert: Bool = false) process.standardError = pipeErr process.launch() - // let data = pipe.fileHandleForReading.readDataToEndOfFile() let dataErr = pipeErr.fileHandleForReading.readDataToEndOfFile() let output = String(data: dataErr, encoding: .utf8) @@ -100,32 +98,11 @@ func checkAndRequestFullDiskAccess(appState: AppState, skipAlert: Bool = false) diskP = false if !skipAlert { NewWin.show(appState: appState, width: 500, height: 350, newWin: .perm) -// appState.alertType = .diskAccess -// appState.showAlert = true } return false } - - -// let fileURL = URL(fileURLWithPath: "/Library/Application Support/com.apple.TCC/TCC.db") -// -// let accessStatus = FileManager.default.isReadableFile(atPath: fileURL.path) -// printOS(accessStatus) -// if accessStatus { -// diskP = true -// _ = checkAndRequestAccessibilityAccess(appState: appState) -// -// return true -// } else { -// diskP = false -// if !skipAlert { -// appState.alertType = .diskAccess -// appState.showAlert = true -// } -// -// return false -// } + } @@ -260,6 +237,13 @@ extension String { } } +// --- Extend string to replace - and | with custom characters +extension String { + func featureFormat() -> String { + return self.replacingOccurrences(of: "- ", with: "• ").replacingOccurrences(of: "|", with: "\n\n") + } +} + // --- Trash Relationship --- extension FileManager { @@ -279,21 +263,6 @@ func printOS(_ items: Any..., separator: String = " ", terminator: String = "\n" } -// --- Gradient --- -func schemeGradient(for colorScheme: ColorScheme) -> LinearGradient { - return LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing) -// switch colorScheme { -// case .light: -// return LinearGradient(gradient: Gradient(colors: [Color("AccentColor")]), startPoint: .leading, endPoint: .trailing) -// case .dark: -// return LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing) -// @unknown default: -// return LinearGradient(gradient: Gradient(colors: [Color("AccentColor")]), startPoint: .leading, endPoint: .trailing) -// } -} - - - // Get total size of folders and files using DU cli command func totalSizeOnDisk(for paths: [URL]) -> Int64 { @@ -354,7 +323,6 @@ extension Array where Element == URL { var totalSize = 0 for path in self { -// if path.absoluteString.contains(".app") { let resourceValues = try path.resourceValues(forKeys: [.isDirectoryKey, .isPackageKey, .totalFileAllocatedSizeKey]) if resourceValues.isDirectory == true || path.pathExtension == "app" { @@ -370,8 +338,7 @@ extension Array where Element == URL { } else { totalSize += resourceValues.totalFileAllocatedSize ?? 0 } -// } - + } return totalSize @@ -396,7 +363,6 @@ func presentAlert(appState: AppState) -> Alert { case .update: return Alert(title: Text("Update Available 🥳"), message: Text("You may choose to install the update now, otherwise you may check again later from Settings"), primaryButton: .default(Text("Install")) { downloadUpdate(appState: appState) -// launchUpdate() appState.alertType = .off }, secondaryButton: .cancel()) case .no_update: @@ -524,11 +490,7 @@ func launchctl(load: Bool) { task.standardError = pipe task.launch() - -// let data = pipe.fileHandleForReading.readDataToEndOfFile() -// if let output = String(data: data, encoding: .utf8) { -// printOS("Output: \(output)") -// } + } } @@ -543,40 +505,5 @@ func getCurrentTimestamp() -> String { } -// Execute sudo command -//func executePrivilegedCommand(launchPath: String, arguments: [String]) { -// let task = Process() -// task.launchPath = launchPath -// task.arguments = arguments -// -// var authorization: AuthorizationRef? = nil -// let status = AuthorizationCreate(nil, nil, AuthorizationFlags(), &authorization) -// -// if status == errAuthorizationSuccess { -// if let rightName = (kAuthorizationRightExecute as NSString).utf8String { -// let item = AuthorizationItem(name: rightName, valueLength: 0, value: nil, flags: 0) -// var items = [item] -// items.withUnsafeMutableBufferPointer { bufferPointer in -// var rights = AuthorizationRights(count: UInt32(bufferPointer.count), items: bufferPointer.baseAddress) -// -// let status = AuthorizationCopyRights(authorization!, &rights, nil, AuthorizationFlags([.extendRights, .interactionAllowed]), nil) -// -// if status == errAuthorizationSuccess { -// task.launch() -// } else { -// printOS("Authorization failed.") -// } -// } -// } else { -// printOS("Failed to convert rightName to C-string.") -// } -// } else { -// printOS("Authorization creation failed.") -// } -//} - -// executePrivilegedCommand(launchPath: "/bin/mv", arguments: ["-f"] + filesSudoPaths + ["\(home)/.Trash"]) - - diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index 2f99b4b..5ec7664 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -21,8 +21,11 @@ struct PearcleanerApp: App { @AppStorage("displayMode") var displayMode: DisplayMode = .system @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.miniview") private var miniView: Bool = true + @AppStorage("settings.general.instant") private var instantSearch: Bool = true + @AppStorage("settings.general.features") private var features: String = "" @State private var search = "" @State private var showPopover: Bool = false + @State private var showFeature: Bool = false var body: some Scene { @@ -30,15 +33,23 @@ struct PearcleanerApp: App { WindowGroup { Group { - - if !mini { - AppListView(search: $search, showPopover: $showPopover) - .environmentObject(locations) - } else { - MiniMode(search: $search, showPopover: $showPopover) - .environmentObject(locations) + ZStack() { + if !mini { + AppListView(search: $search, showPopover: $showPopover) + .environmentObject(locations) + } else { + MiniMode(search: $search, showPopover: $showPopover) + .environmentObject(locations) + } + + if showFeature { + NewFeatureView(text: features, mini: mini, showFeature: $showFeature) + .transition(.opacity) + } + } - + + } .environmentObject(appState) .preferredColorScheme(displayMode.colorScheme) @@ -85,19 +96,22 @@ struct PearcleanerApp: App { appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps.systemApps = sortedApps.systemApps - + // Find all app paths on load + if instantSearch { + loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) + } + + getFeatures(appState: appState, show: $showFeature, features: $features) #if !DEBUG Task { - // Find all app paths on load - loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) // Make sure App Support folder exists in the future if needed for storage ensureApplicationSupportFolderExists(appState: appState) - // Check for updates 1 minute after app launch + // Check for updates after app launch if diskP { loadGithubReleases(appState: appState) } @@ -111,7 +125,7 @@ struct PearcleanerApp: App { // TIMERS //////////////////////////////////////////////////////////////////////////////////// - // Check for app updates every 8 hours or whatever user saved setting. Also refresh autosuggestion list + // Check for app updates every 8 hours or whatever user saved setting. let updateSeconds = updateTimeframe.daysToSeconds _ = Timer.scheduledTimer(withTimeInterval: updateSeconds, repeats: true) { _ in DispatchQueue.main.async { diff --git a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index 64dc11e..45a5931 100644 --- a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -7,7 +7,6 @@ "size" : "16x16" }, { - "filename" : "icon_16x16@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" @@ -19,7 +18,6 @@ "size" : "32x32" }, { - "filename" : "icon_32x32@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" @@ -31,7 +29,6 @@ "size" : "128x128" }, { - "filename" : "icon_128x128@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" @@ -43,7 +40,6 @@ "size" : "256x256" }, { - "filename" : "icon_256x256@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" @@ -55,7 +51,6 @@ "size" : "512x512" }, { - "filename" : "icon_512x512@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" diff --git a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png deleted file mode 100644 index a913a26..0000000 Binary files a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png and /dev/null differ diff --git a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png deleted file mode 100644 index 215a2ac..0000000 Binary files a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png and /dev/null differ diff --git a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png deleted file mode 100644 index b47cc54..0000000 Binary files a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png and /dev/null differ diff --git a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png deleted file mode 100644 index 596ad2b..0000000 Binary files a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png and /dev/null differ diff --git a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png deleted file mode 100644 index 80272cf..0000000 Binary files a/Pearcleaner/Resources/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png and /dev/null differ diff --git a/Pearcleaner/Resources/Assets.xcassets/logo.imageset/Contents.json b/Pearcleaner/Resources/Assets.xcassets/logo.imageset/Contents.json deleted file mode 100644 index 3fb7627..0000000 --- a/Pearcleaner/Resources/Assets.xcassets/logo.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "logo_combo_small.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Pearcleaner/Resources/Assets.xcassets/logo.imageset/logo_combo_small.png b/Pearcleaner/Resources/Assets.xcassets/logo.imageset/logo_combo_small.png deleted file mode 100644 index 8255dc0..0000000 Binary files a/Pearcleaner/Resources/Assets.xcassets/logo.imageset/logo_combo_small.png and /dev/null differ diff --git a/Pearcleaner/Resources/Assets.xcassets/pear.colorset/Contents.json b/Pearcleaner/Resources/Assets.xcassets/pear.colorset/Contents.json new file mode 100644 index 0000000..8e9a1c7 --- /dev/null +++ b/Pearcleaner/Resources/Assets.xcassets/pear.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x50", + "green" : "0xC3", + "red" : "0x79" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0x50", + "green" : "0xC3", + "red" : "0x79" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pearcleaner/Resources/Assets.xcassets/pear.imageset/Contents.json b/Pearcleaner/Resources/Assets.xcassets/pear.imageset/Contents.json deleted file mode 100644 index 9460316..0000000 --- a/Pearcleaner/Resources/Assets.xcassets/pear.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "pear_small.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Pearcleaner/Resources/Assets.xcassets/pear.imageset/pear_small.png b/Pearcleaner/Resources/Assets.xcassets/pear.imageset/pear_small.png deleted file mode 100644 index 5f4b01f..0000000 Binary files a/Pearcleaner/Resources/Assets.xcassets/pear.imageset/pear_small.png and /dev/null differ diff --git a/Pearcleaner/Settings/About.swift b/Pearcleaner/Settings/About.swift index ac79a76..4d5fbed 100644 --- a/Pearcleaner/Settings/About.swift +++ b/Pearcleaner/Settings/About.swift @@ -36,10 +36,11 @@ struct AboutSettingsTab: View { VStack(alignment: .leading) { HStack() { - Text("Credits and Inspiration").font(.title2) + Text("Credits").font(.title2) Spacer() } .padding(.leading) + .padding(.bottom) HStack{ Image(systemName: "paintbrush.pointed") diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index 40a6d6b..526e041 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -19,9 +19,10 @@ struct GeneralSettingsTab: View { @AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280 @AppStorage("displayMode") var displayMode: DisplayMode = .system @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false + @AppStorage("settings.general.instant") private var instantSearch: Bool = true + @AppStorage("settings.general.selectedTheme") var selectedTheme: String = "Auto" @State private var diskStatus: Bool = false @State private var accessStatus: Bool = false - @State private var selectedTheme = "Auto" private let themes = ["Auto", "Dark", "Light"] @Binding var showPopover: Bool @@ -57,25 +58,24 @@ struct GeneralSettingsTab: View { HStack(spacing: 0) { - Image(systemName: "sidebar.left") + Image(systemName: instantSearch ? "bolt" : "bolt.slash") .resizable() .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) .foregroundStyle(.gray) - Text("Adjust sidebar width") - .font(.callout) - .foregroundStyle(.gray) + VStack(alignment: .leading, spacing: 5) { + Text("\(instantSearch ? "Instant search is enabled" : "Instant search is disabled")") + .font(.callout) + .foregroundStyle(.gray) + } + + InfoButton(text: "When instant search is enabled, all application files are gathered and cached for later use on startup instead of on each app click. There might be a slight delay of a few seconds when launching Pearcleaner depending on the amount of apps you have installed.", color: nil) + Spacer() - Slider(value: $sidebarWidth, in: 200...360) { -// Text("\(Int(sidebarWidth))") - } - .padding(.horizontal) - .frame(width: 185) - Button("") { - sidebarWidth = 280 - } - .buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Reset to default size", color: Color("mode"))) + Toggle(isOn: $instantSearch, label: { + }) + .toggleStyle(.switch) } .padding(5) .padding(.leading) @@ -89,7 +89,7 @@ struct GeneralSettingsTab: View { .padding(.trailing) .foregroundStyle(.gray) VStack(alignment: .leading, spacing: 5) { - Text("Set color mode") + Text("Set application color mode") .font(.callout) .foregroundStyle(.gray) } @@ -143,7 +143,7 @@ struct GeneralSettingsTab: View { .padding(.trailing) .foregroundStyle(.gray) VStack(alignment: .leading, spacing: 5) { - Text("\(mini ? "Mini window mode" : "Full size window mode")") + Text("\(mini ? "Mini window mode selected" : "Full size window mode selected")") .font(.callout) .foregroundStyle(.gray) } @@ -182,6 +182,9 @@ struct GeneralSettingsTab: View { .font(.callout) .foregroundStyle(.gray) } + + InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.", color: nil) + Spacer() Toggle(isOn: $miniView, label: { }) @@ -202,10 +205,13 @@ struct GeneralSettingsTab: View { .padding(.trailing) .foregroundStyle(.gray) VStack(alignment: .leading, spacing: 5) { - Text("\(popoverStay ? "Popover window doesn't hide on outside click" : "Popover window hides on outside click")") + Text("\(popoverStay ? "Popover window will stay on top" : "Popover window will not stay on top")") .font(.callout) .foregroundStyle(.gray) } + + InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the popover.", color: nil) + Spacer() Toggle(isOn: $popoverStay, label: { }) @@ -330,6 +336,11 @@ struct GeneralSettingsTab: View { .onAppear { diskStatus = checkAndRequestFullDiskAccess(appState: appState, skipAlert: true) accessStatus = checkAndRequestAccessibilityAccess(appState: appState) +// if displayMode.colorScheme == .dark { +// selectedTheme = "Dark" +// } else if displayMode.colorScheme == .light { +// selectedTheme = "Light" +// } } } diff --git a/Pearcleaner/Settings/Permissions.swift b/Pearcleaner/Settings/Permissions.swift deleted file mode 100644 index 3dd96a0..0000000 --- a/Pearcleaner/Settings/Permissions.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// Permissions.swift -// Pearcleaner -// -// Created by Alin Lupascu on 11/5/23. -// - -import Foundation -import SwiftUI - -struct PermissionsSettingsTab: View { - @EnvironmentObject var appState: AppState - @State private var diskStatus: Bool = false - @State private var accessStatus: Bool = false - - - - var body: some View { - Form { - VStack(alignment: .leading, spacing: 10) { - - HStack(spacing: 0) { - Image(systemName: diskStatus ? "externaldrive" : "externaldrive") - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - .padding(.trailing) - .foregroundStyle(diskStatus ? .green : .red) - Text(diskStatus ? "Full Disk permission granted" : "Full Disk permission not granted") - .font(.callout) - .foregroundStyle(.gray) - Spacer() - - Button("") { - if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") { - NSWorkspace.shared.open(url) - } - } - .buttonStyle(SimpleButtonStyle(icon: "gear", help: "View disk permissions pane", color: Color("mode"))) - - } - .padding(5) - - - HStack(spacing: 0) { - Image(systemName: accessStatus ? "accessibility" : "accessibility") - .resizable() - .scaledToFit() - .frame(width: 20, height: 20) - .padding(.trailing) - .foregroundStyle(accessStatus ? .green : .red) - Text(accessStatus ? "Accessibility permission granted" : "Accessibility permission not granted") - .font(.callout) - .foregroundStyle(.gray) - Spacer() - - Button("") { - if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") { - NSWorkspace.shared.open(url) - } - } - .buttonStyle(SimpleButtonStyle(icon: "gear", help: "View accessibility permissions pane", color: Color("mode"))) - - } - .padding(5) - - -// HStack(spacing: 15) { -// Image(systemName: "internaldrive") -// .resizable() -// .scaledToFit() -// .frame(width: 20) -// -// VStack(alignment: .leading, spacing: 5) { -// Text("Full Disk") -// Text(diskStatus ? "Permission granted" : "Permission not granted") -// .font(.footnote) -// .foregroundStyle(diskStatus ? .green : .red) -// } -// -// Spacer() -// -// Button("") { -// if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") { -// NSWorkspace.shared.open(url) -// } -// } -// .buttonStyle(SimpleButtonStyle(icon: "paperplane", help: "View disk permission pane", color: Color("AccentColor"))) -// } -// .padding() -// .background( -// RoundedRectangle(cornerRadius: 8) -// .fill(Color("mode").opacity(0.05)) -// ) - - -// HStack(spacing: 15) { -// Image(systemName: "accessibility") -// .resizable() -// .scaledToFit() -// .frame(width: 20) -// -// VStack(alignment: .leading, spacing: 5) { -// Text("Accessibility") -// Text(accessStatus ? "Permission granted" : "Permission not granted") -// .font(.footnote) -// .foregroundStyle(accessStatus ? .green : .red) -// } -// -// Spacer() -// -// Button("") { -// if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") { -// NSWorkspace.shared.open(url) -// } -// } -// .buttonStyle(SimpleButtonStyle(icon: "paperplane", help: "View accessibility pane", color: Color("AccentColor"))) -// } -// .padding() -// .background( -// RoundedRectangle(cornerRadius: 8) -// .fill(Color("mode").opacity(0.05)) -// ) - - - -// HStack(alignment: .center) { -// Spacer() -// Button("") { -// relaunchApp() -// } -// .buttonStyle(SimpleButtonStyle(icon: "restart", help: "Restart application", color: Color("AccentColor"))) -// Text("Restart") -// Spacer() -// } -// .padding() - - - } - - } - .padding(20) - .frame(width: 400, height: 200) - .onAppear { - diskStatus = checkAndRequestFullDiskAccess(appState: appState, skipAlert: true) - accessStatus = checkAndRequestAccessibilityAccess(appState: appState) - } - } - -} diff --git a/Pearcleaner/Settings/Sentinel.swift b/Pearcleaner/Settings/Sentinel.swift deleted file mode 100644 index 97d14af..0000000 --- a/Pearcleaner/Settings/Sentinel.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Sentinel.swift -// Pearcleaner -// -// Created by Alin Lupascu on 11/9/23. -// - -import Foundation -import SwiftUI - -struct SentinelSettingsTab: View { - @EnvironmentObject var appState: AppState - @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false - - var body: some View { - Form { - VStack { - HStack { - VStack(alignment: .leading, spacing: 5) { - Text("Sentinel Monitor").font(.title2) - Text("Detects when apps are moved to Trash for file removal") - .font(.footnote) - .foregroundStyle(.gray) - } - Spacer() - Toggle(isOn: $sentinel, label: { - }) - .toggleStyle(SentinelToggleStyle()) - .onChange(of: sentinel) { newValue in - if newValue { - launchctl(load: true) - } else { - launchctl(load: false) - } - } - } - .padding() - .background( - RoundedRectangle(cornerRadius: 8) - .fill(Color("mode").opacity(0.05)) - ) - Text("If you uninstall Pearcleaner, make sure to disable Sentinel first").font(.footnote) - .foregroundStyle(Color.red) - .padding() - - } - - - - } - .padding(20) - .frame(width: 450, height: 130) - - } - -} diff --git a/Pearcleaner/Settings/SettingsWindow.swift b/Pearcleaner/Settings/SettingsWindow.swift index 5d69537..87e8376 100644 --- a/Pearcleaner/Settings/SettingsWindow.swift +++ b/Pearcleaner/Settings/SettingsWindow.swift @@ -20,18 +20,6 @@ struct SettingsView: View { } .tag(CurrentTabView.general) -// PermissionsSettingsTab() -// .tabItem { -// Label(CurrentTabView.permissions.title, systemImage: "lock") -// } -// .tag(CurrentTabView.permissions) - -// SentinelSettingsTab() -// .tabItem { -// Label(CurrentTabView.sentinel.title, systemImage: "eye.circle") -// } -// .tag(CurrentTabView.sentinel) - UpdateSettingsTab() .tabItem { Label(CurrentTabView.update.title, systemImage: "cloud") @@ -45,7 +33,7 @@ struct SettingsView: View { .tag(CurrentTabView.about) } - + } } @@ -56,3 +44,5 @@ struct SettingsView: View { + + diff --git a/Pearcleaner/Settings/Update.swift b/Pearcleaner/Settings/Update.swift index d792687..8d43dc0 100644 --- a/Pearcleaner/Settings/Update.swift +++ b/Pearcleaner/Settings/Update.swift @@ -71,23 +71,25 @@ struct UpdateSettingsTab: View { HStack(alignment: .center, spacing: 20) { Spacer() + Button("Update Notes"){ loadGithubReleases(appState: appState) } .buttonStyle(SimpleButtonStyle(icon: "list.bullet", help: "Check release notes", color: Color("mode"))) + Spacer() Button("Force Update"){ loadGithubReleases(appState: appState, manual: true) } .buttonStyle(SimpleButtonStyle(icon: "arrow.down.square", help: "Check for updates", color: Color("mode"))) + Spacer() Button("GitHub"){ NSWorkspace.shared.open(URL(string: "https://github.com/alienator88/Pearcleaner/releases")!) } .buttonStyle(SimpleButtonStyle(icon: "link", help: "View releases on GitHub", color: Color("mode"))) - Spacer() } @@ -130,13 +132,9 @@ func checkForUpdate(appState: AppState, manual: Bool = false) { let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String if latestRelease.tag_name > currentVersion ?? "" { NewWin.show(appState: appState, width: 500, height: 440, newWin: .update) -// appState.alertType = .update -// appState.showAlert = true } else { if manual { NewWin.show(appState: appState, width: 500, height: 300, newWin: .no_update) -// appState.alertType = .no_update -// appState.showAlert = true } } } @@ -215,22 +213,14 @@ func UnzipAndReplace(DownloadedFileURL fileURL: String, appState: AppState) { // Unzip the downloaded update file to your app's bundle path let process = Process() -// process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip") -// process.arguments = [fileURL, "-d", appDirectory] process.executableURL = URL(fileURLWithPath: "/usr/bin/ditto") process.arguments = ["-xk", fileURL, appDirectory] -// let pipe = Pipe() process.standardOutput = FileHandle.nullDevice process.standardError = FileHandle.nullDevice try process.run() process.waitUntilExit() - -// let outputData = pipe.fileHandleForReading.readDataToEndOfFile() -// let output = String(decoding: outputData, as: UTF8.self) -// -// writeLog(string: output) - + updateOnMain { appState.progressBar.0 = "Deleting update file" appState.progressBar.1 = 0.8 @@ -239,12 +229,6 @@ func UnzipAndReplace(DownloadedFileURL fileURL: String, appState: AppState) { // After unzipping, remove the update file try fileManager.removeItem(atPath: fileURL) - // Show Restart dialog -// updateOnMain { -// appState.alertType = .restartApp -// appState.showAlert = true -// } - } catch { printOS("Error updating the app: \(error)") diff --git a/Pearcleaner/Views/AppListItems.swift b/Pearcleaner/Views/AppListItems.swift index 3f60619..e78ce99 100644 --- a/Pearcleaner/Views/AppListItems.swift +++ b/Pearcleaner/Views/AppListItems.swift @@ -26,8 +26,18 @@ struct AppListItems: View { var body: some View { VStack(alignment: .leading, spacing: 5) { + + + HStack(alignment: .center) { + if isHovered || isSelected { + RoundedRectangle(cornerRadius: 50) + .fill(isSelected ? Color("pear") : Color("mode").opacity(0.5)) + .frame(width: isSelected ? 4 : 2, height: 25) + .padding(.trailing, 5) + } + if let appIcon = appInfo.appIcon { Image(nsImage: appIcon) .resizable() @@ -35,6 +45,7 @@ struct AppListItems: View { .frame(width: 30, height: 30) .clipShape(RoundedRectangle(cornerRadius: 8)) } + VStack(alignment: .leading, spacing: 5) { Text(appInfo.appName) .font(.system(size: 12)) @@ -56,8 +67,6 @@ struct AppListItems: View { .background( Capsule().strokeBorder(Color("mode").opacity(0.3), lineWidth: 1) ) -// .background(Color("mode").opacity(0.1)) -// .clipShape(.capsule) } if appInfo.wrapped { Text("iOS") @@ -68,24 +77,14 @@ struct AppListItems: View { .background( Capsule().strokeBorder(Color("mode").opacity(0.3), lineWidth: 1) ) -// .background(Color("mode").opacity(0.1)) -// .clipShape(.capsule) } Text(appInfo.appVersion) .font(.footnote) .foregroundStyle(Color("mode").opacity(0.5)) - - if isHovered || isSelected { - RoundedRectangle(cornerRadius: 50) - .fill(isSelected ? Color("AccentColor") : Color("mode").opacity(0.5)) - .frame(width: isSelected ? 4 : 2, height: 30) - .padding(.leading, 5) - .offset(x: 20) - } } - + } .padding(.horizontal, 5) .help(appInfo.appName) @@ -97,25 +96,5 @@ struct AppListItems: View { .onTapGesture { showAppInFiles(appInfo: appInfo, mini: mini, appState: appState, locations: locations, showPopover: $showPopover) } - -// .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) -// -// } } } diff --git a/Pearcleaner/Views/AppListView.swift b/Pearcleaner/Views/AppListView.swift index 586e032..45b7c3c 100644 --- a/Pearcleaner/Views/AppListView.swift +++ b/Pearcleaner/Views/AppListView.swift @@ -12,12 +12,12 @@ 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 + @AppStorage("settings.general.instant") private var instantSearch: Bool = true @Binding var search: String @State private var showSys: Bool = true @State private var showUsr: Bool = true -// @State private var sidebar: Bool = true @Binding var showPopover: Bool - + var filteredUserApps: [AppInfo] { if search.isEmpty { return appState.sortedApps.userApps @@ -25,7 +25,7 @@ struct AppListView: View { return appState.sortedApps.userApps.filter { $0.appName.localizedCaseInsensitiveContains(search) } } } - + var filteredSystemApps: [AppInfo] { if search.isEmpty { return appState.sortedApps.systemApps @@ -33,87 +33,87 @@ struct AppListView: View { return appState.sortedApps.systemApps.filter { $0.appName.localizedCaseInsensitiveContains(search) } } } - + var body: some View { - + HStack(alignment: .center, spacing: 0) { // App List - if appState.sidebar { - ZStack { - HStack(spacing: 0){ - - if appState.reload { - VStack { - Spacer() - ProgressView("Loading apps and files") - Spacer() - } - .frame(width: sidebarWidth) - .padding(.vertical) - } else { - VStack(alignment: .center) { - - VStack(alignment: .center, spacing: 20) { - HStack { - SearchBarMiniBottom(search: $search) + HStack(spacing: 0){ - } - } - .padding(.top, 20) - .padding(.bottom) - - ScrollView { - - LazyVStack(alignment: .leading, pinnedViews: [.sectionHeaders]) { - - if filteredUserApps.count > 0 { - VStack { - Header(title: "User", count: filteredUserApps.count, showPopover: $showPopover) - ForEach(filteredUserApps, id: \.self) { appInfo in - AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) - if appInfo != filteredUserApps.last { - Divider().padding(.horizontal, 5) - } - } -// .padding(.bottom) - } - - } - - if filteredSystemApps.count > 0 { - VStack { - Header(title: "System", count: filteredSystemApps.count, showPopover: $showPopover) - ForEach(filteredSystemApps, id: \.self) { appInfo in - AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) - if appInfo != filteredSystemApps.last { - Divider().padding(.horizontal, 5) - } - } - } - } - - } - .padding(.horizontal) - - } - .scrollIndicators(.never) - - } - .frame(width: sidebarWidth) - .padding(.vertical) + if appState.reload { + VStack { + Spacer() + ProgressView(instantSearch ? "Refreshing app list and caching files" : "Refreshing app list") + if instantSearch { + Image(systemName: "bolt") + .resizable() + .scaledToFit() + .frame(width: 30, height: 30) } - - - Divider().foregroundStyle(.red) + Spacer() } + .frame(width: sidebarWidth) + .padding(.vertical) + } else { + VStack(alignment: .center) { + + VStack(alignment: .center, spacing: 20) { + HStack { + SearchBarMiniBottom(search: $search) + + } + } + .padding(.top, 20) + .padding(.bottom) + + ScrollView { + + LazyVStack(alignment: .leading, pinnedViews: [.sectionHeaders]) { + + if filteredUserApps.count > 0 { + VStack { + Header(title: "User", count: filteredUserApps.count, showPopover: $showPopover) + ForEach(filteredUserApps, id: \.self) { appInfo in + AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) + if appInfo != filteredUserApps.last { + Divider().padding(.horizontal, 5) + } + } + } + + } + + if filteredSystemApps.count > 0 { + VStack { + Header(title: "System", count: filteredSystemApps.count, showPopover: $showPopover) + ForEach(filteredSystemApps, id: \.self) { appInfo in + AppListItems(search: $search, showPopover: $showPopover, appInfo: appInfo) + if appInfo != filteredSystemApps.last { + Divider().padding(.horizontal, 5) + } + } + } + } + + } + .padding(.horizontal) + + } + .scrollIndicators(.never) + + } + .frame(width: sidebarWidth) + .padding(.vertical) } - .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) - .transition(.move(edge: .leading)) - - Spacer() + + } - - + .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) + .transition(.move(edge: .leading)) + + SlideableDivider(dimension: $sidebarWidth) + + // Details View VStack(spacing: 0) { if appState.currentView == .empty || appState.currentView == .apps { @@ -130,12 +130,19 @@ struct AppListView: View { } } .transition(.move(edge: .leading)) - - Spacer() } .frame(minWidth: 900, minHeight: 600) .edgesIgnoringSafeArea(.all) + // MARK: Background for whole app +// .background{ +// Image("peakpx") +// .resizable() +// .scaledToFill() +// .edgesIgnoringSafeArea(.all) +// GlassEffect(material: .sidebar, blendingMode: .withinWindow) +// .edgesIgnoringSafeArea(.all) +// } // .background(Color("bg").opacity(1)) // .background(VisualEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all)) } @@ -157,7 +164,7 @@ struct AppDetailsEmptyView: View { VStack(alignment: .center) { Spacer() - + PearDropView() Spacer() @@ -173,7 +180,7 @@ struct AppDetailsEmptyView: View { struct SearchBar: View { @Binding var search: String - + var body: some View { HStack { TextField("Search", text: $search) @@ -191,12 +198,12 @@ struct Header: View { @EnvironmentObject var appState: AppState @EnvironmentObject var locations: Locations @Binding var showPopover: Bool + @AppStorage("settings.general.instant") private var instantSearch: Bool = true + var body: some View { HStack { - Text(title).opacity(0.5) - -// Spacer() + Text(title).foregroundStyle(Color("mode")).opacity(0.5) HStack { if hovered { @@ -214,8 +221,15 @@ struct Header: View { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps.systemApps = sortedApps.systemApps - loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) - appState.reload.toggle() + if instantSearch { + loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) { + appState.reload.toggle() + + } + } else { + appState.reload.toggle() + } + } } } @@ -228,7 +242,6 @@ struct Header: View { .padding(2) .background(Color("mode").opacity(0.1)) .clipShape(.capsule) - // .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous)) } } .onHover { hovering in @@ -240,6 +253,7 @@ struct Header: View { Spacer() } - .padding(4) + .padding(.vertical, 8) + .padding(.leading, 6) } } diff --git a/Pearcleaner/Views/DropTarget.swift b/Pearcleaner/Views/DropTarget.swift deleted file mode 100644 index 106cafb..0000000 --- a/Pearcleaner/Views/DropTarget.swift +++ /dev/null @@ -1,231 +0,0 @@ -// -// DropTarget.swift -// Pearcleaner -// -// Created by Alin Lupascu on 11/11/23. -// - -import Foundation -import SwiftUI -import UniformTypeIdentifiers - -struct DropTarget: View, DropDelegate { - @AppStorage("settings.general.ants") private var ants: Bool = false - @AppStorage("settings.general.mini") private var mini: Bool = false - @ObservedObject var appState: AppState - @ObservedObject var locations: Locations - @State private var shouldFlash = false - private var types: [UTType] = [.fileURL] - @Environment(\.colorScheme) var colorScheme - @State private var phase: CGFloat = 4 - @Binding var showPopover: Bool - - public init(appState: AppState, locations: Locations, showPopover: Binding) { - self.appState = appState - self.locations = locations - _showPopover = showPopover - } - - var body: some View { - ZStack { - - FlashingBackground(shouldFlash: $shouldFlash) - - HStack { - Spacer() - - HStack { - Spacer() - - ZStack { -// Color("mode") -// .mask( -// Image(mini ? "pear" : "logo") -// .resizable() -// .scaledToFit() -// .frame(minWidth: mini ? 104 : 300) -// .frame(maxWidth: mini ? 104 : 500) -// .padding(mini ? 20 : 10) -// ) - LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing) - .mask( - Image(mini ? "pear" : "logo") - .resizable() - .scaledToFit() - .frame(minWidth: mini ? 104 : 300) - .frame(maxWidth: mini ? 104 : 500) - .padding(mini ? 20 : 10) - ) - } - - Spacer() - - } - - Spacer() - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background { - RoundedRectangle(cornerRadius: 8) -// .fill(LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing)) - .strokeBorder(LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing), style: StrokeStyle(lineWidth: 3, dash: [11.0], dashPhase: phase)) - .onAppear { - withAnimation(.linear.speed(0.5).repeat(while: ants, autoreverses: false)) { - if ants { - phase -= 22 - } - } - } - .onChange(of: ants) { newValue in - withAnimation(.linear.speed(0.5).repeat(while: newValue, autoreverses: false)) { - phase -= 22 - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - - } - - - } - .onDrop(of: types, delegate: self) - .onTapGesture { - withAnimation(.easeInOut(duration: 0.5)) { - appState.currentView = .apps - } - } - .padding(20) - .frame(maxWidth: mini ? 200 : .infinity) - .frame(maxHeight: mini ? 200 : .infinity) - - } - - func dropEntered(info: DropInfo) { - DispatchQueue.main.async { - self.ants = true - } - } - - func dropExited(info: DropInfo) { - DispatchQueue.main.async { - self.ants = false - } - } - - func performDrop(info: DropInfo) -> Bool { - - let itemProviders = info.itemProviders(for: [UTType.fileURL]) - - guard itemProviders.count == 1 else { - return false - } - for itemProvider in itemProviders { - itemProvider.loadItem(forTypeIdentifier: UTType.fileURL.identifier, options: nil) { item, error in - guard let data = item as? Data else { - dump(error) - return - } - guard let url = URL(dataRepresentation: data, relativeTo: nil) else { - printOS("Error: Not a valid URL.") - return - } - if url.pathExtension == "app" { - let appInfo = getAppInfo(atPath: url) -// showPopover = false - self.ants = false - showAppInFiles(appInfo: appInfo!, mini: mini, appState: appState, locations: locations, showPopover: $showPopover) - -// DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { -// updateOnMain { -// appState.appInfo = .empty -// if let storedAppInfo = appState.appInfoStore.first(where: { $0.path == appInfo!.path }) { -// appState.appInfo = storedAppInfo -// // appState.paths = storedAppInfo.fileSize.keys.map { $0 }//storedAppInfo.files -// appState.selectedItems = Set(storedAppInfo.files) -// withAnimation(Animation.easeIn(duration: 0.4)) { -// if mini { -// showPopover.toggle() -// } else { -// appState.currentView = .files -// } -// } -// } else { -// // Handle the case where the appInfo is not found in the store -// printOS("AppInfo not found in the cached store, searching again") -// appState.appInfo = .empty -// appState.appInfo = appInfo! -// findPathsForApp(appInfo: appInfo!, appState: appState, locations: locations) -// withAnimation(Animation.easeIn(duration: 0.4)) { -// if mini { -// showPopover.toggle() -// } else { -// appState.currentView = .files -// } -// } -// } -// } -// -// } -// DispatchQueue.main.async { -// self.ants = false -// appState.appInfo = appInfo! -// findPathsForApp(appState: appState, locations: locations) -// if mini { -// showPopover = true -// } else { -// appState.currentView = .files -// } -// } - } else { - printOS("Error: Dropped file is not an application bundle") - DispatchQueue.main.async { - self.shouldFlash = true - } - } - - } - } - DispatchQueue.main.async { - self.ants = false - } - return true - } - - -} - - -struct FlashingBackground: View { - @Binding var shouldFlash: Bool - - var body: some View { - GeometryReader { geometry in - Rectangle() - .fill(shouldFlash ? Color.red : Color.clear) - .cornerRadius(8) - .padding() - .opacity(shouldFlash ? 0.5 : 0) - .animation(.easeInOut(duration: 0.2).repeatCount(1, autoreverses: true), value: shouldFlash) - .onChange(of: shouldFlash) { newValue in - if newValue { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - shouldFlash = false - } - } - } - .ignoresSafeArea() - .frame(width: geometry.size.width, height: geometry.size.height) - } - } -} - - - -extension Animation { - func `repeat`(while expression: Bool, autoreverses: Bool = true) -> Animation { - if expression { - return self.repeatForever(autoreverses: autoreverses) - } else { - return self - } - } -} diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index 0d3de5c..bd20307 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -21,13 +21,10 @@ struct FilesView: View { var body: some View { VStack(alignment: .center) { - if appState.showProgress { //!self.showDetails { + if appState.showProgress { VStack { Spacer() -// ProgressView("Finding application files..") -// .progressViewStyle(.linear) -// Spacer() - Text("Almost there, still gathering files..").font(.title3) + Text("Searching the file system").font(.title3) .foregroundStyle((.gray.opacity(0.8))) ProgressView() .progressViewStyle(.linear) @@ -41,56 +38,51 @@ struct FilesView: View { VStack() { // Main Group - HStack() { - //icon - if let appIcon = appState.appInfo.appIcon { - Image(nsImage: appIcon) - .resizable() - .scaledToFit() -// .aspectRatio(contentMode: .fit) - .frame(width: 50, height: 50) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .padding(.leading) - } - //app title, size and items + HStack(alignment: .center) { + + //app icon, title, size and items VStack(alignment: .center) { HStack(alignment: .center) { + if let appIcon = appState.appInfo.appIcon { + Image(nsImage: appIcon) + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .padding(.trailing) + } + VStack(alignment: .leading, spacing: 5){ HStack { Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold) -// .foregroundStyle(Color("AccentColor")) Text("•").foregroundStyle(Color("AccentColor")) Text("\(appState.appInfo.appVersion)").font(.title3) -// .foregroundStyle(.gray.opacity(0.8)) + if appState.appInfo.appName.count < 5 { + InfoButton(text: "Pearcleaner searches for files via a combination of bundle id and app name. \(appState.appInfo.appName) has a common or short app name so there might be unrelated files found. Please check the list thoroughly before uninstalling.", color: nil) + } + } Text("\(appState.appInfo.bundleIdentifier)").font(.title3) .foregroundStyle((.gray.opacity(0.8))) } - + Spacer() - - Text("\(formatByte(size: appState.appInfo.totalSize))").font(.title).fontWeight(.bold) + + VStack(alignment: .trailing, spacing: 5) { + Text("\(formatByte(size: appState.appInfo.totalSize))").font(.title).fontWeight(.bold) + Text("\(appState.appInfo.fileSize.count > 1 ? "\(appState.appInfo.fileSize.count) items" : "\(appState.appInfo.fileSize.count) item")").font(.callout).underline().foregroundStyle((.gray.opacity(0.8))) + } } - - - Divider().padding(.bottom, 7) - - HStack{ - Text("Application files and folders") - .font(.callout) -// .foregroundStyle(Color("AccentColor")) - .opacity(0.8) - Spacer() - Text("\(appState.appInfo.fileSize.count > 1 ? "\(appState.appInfo.fileSize.count) items" : "\(appState.appInfo.fileSize.count) item")").font(.callout) -// .foregroundStyle(Color("AccentColor").opacity(0.7)) - .underline() - } - HStack() { - if appState.appInfo.webApp { - HStack { + + if appState.appInfo.webApp || appState.appInfo.wrapped { + HStack(alignment: .center, spacing: 10) { + + Spacer() + + if appState.appInfo.webApp { Text("web") .font(.footnote) .foregroundStyle(Color("mode").opacity(0.5)) @@ -98,34 +90,33 @@ struct FilesView: View { .padding(2) .background(Color("mode").opacity(0.1)) .clipShape(.capsule) - Spacer() + + } + + if appState.appInfo.wrapped { + Text("iOS") + .font(.footnote) + .foregroundStyle(Color("mode").opacity(0.5)) + .frame(minWidth: 30, minHeight: 15) + .padding(2) + .background(Color("mode").opacity(0.1)) + .clipShape(.capsule) + } } - if appState.appInfo.appName.count < 5 { - WarningPopoverView(label: "Caution", - bodyText: "Pearcleaner searches for files via a combination of bundle id and app name.\n\(appState.appInfo.appName) has a common or short app name so there might be unrelated files found.\nPlease check the list thoroughly before uninstalling.", - isPresented: $showPop) - } } - .padding(.vertical, 5) - - + + + } - .padding(20) + .padding(.horizontal, 20) + .padding(.top, 10) } -// .padding(.bottom) - .background( - RoundedRectangle(cornerRadius: 8) -// .strokeBorder(Color("AccentColor"), lineWidth: 0.5) - .fill(Color("mode").opacity(colorScheme == .dark ? 0.05 : 0.05)) -// .background( -// RoundedRectangle(cornerRadius: 8) -// .strokeBorder(Color("AccentColor").opacity(colorScheme == .dark ? 0.1 : 0.1), lineWidth: 1) -// ) - ) - .padding(.bottom) + + Divider() + .padding() ScrollView() { VStack { @@ -144,29 +135,13 @@ struct FilesView: View { Divider().padding(.leading, 40) } } -// .padding(.leading, 47).padding(.trailing) -// if let firstPath = appState.appInfo.files.first, path == firstPath { -// FileDetailsItem(size: fileSize, icon: iconImage, path: path) -// .padding(.trailing) -// Divider().padding(.leading, 40).padding(.trailing) -// } else { -// VStack { -// FileDetailsItem(size: fileSize, icon: iconImage, path: path) -// if path != appState.appInfo.files.last { -// Divider().padding(.leading, 40) -// } -// } -// .padding(.leading, 47).padding(.trailing) -// } } } } .padding() } -// .background( -// RoundedRectangle(cornerRadius: 8) -// .fill(Color("mode").opacity(colorScheme == .dark ? 0.05 : 0.05)) -// ) + + Spacer() HStack() { @@ -187,7 +162,7 @@ struct FilesView: View { .help("Item Selection") Spacer() - + if mini { Button("Close") { @@ -202,58 +177,59 @@ struct FilesView: View { } - - Button("Uninstall") { - Task { - updateOnMain { - appState.appInfo = AppInfo.empty - search = "" - if mini { - appState.currentView = .apps - showPopover = false - } else { - appState.currentView = .empty - } - } - - var selectedItemsArray = Array(appState.selectedItems) - .filter { !$0.path.contains(".Trash") } - .map { path in - return path.path.contains("Wrapper") ? path.deletingLastPathComponent().deletingLastPathComponent() : path - } - - if let url = URL(string: appState.appInfo.path.absoluteString) { - let appFolderURL = url.deletingLastPathComponent() // Get the immediate parent directory - - if appFolderURL.path == "/Applications" || appFolderURL.path == "\(home)/Applications" { - // Do nothing, skip insertion - } else if appFolderURL.pathComponents.count > 2 && appFolderURL.path.contains("Applications") { - // Insert into selectedItemsArray only if there is an intermediary folder - selectedItemsArray.insert(appFolderURL, at: 0) - } - } - - - killApp(appId: appState.appInfo.bundleIdentifier) { - moveFilesToTrash(at: selectedItemsArray) { - withAnimation { + if !appState.selectedItems.isEmpty { + Button("Uninstall") { + Task { + updateOnMain { + appState.appInfo = AppInfo.empty + search = "" + if mini { + appState.currentView = .apps showPopover = false - updateOnMain { - appState.isReminderVisible.toggle() - if sentinel { - launchctl(load: true) + } else { + appState.currentView = .empty + } + } + var selectedItemsArray = Array(appState.selectedItems) + .filter { !$0.path.contains(".Trash") } + .map { path in + return path.path.contains("Wrapper") ? path.deletingLastPathComponent().deletingLastPathComponent() : path + } + + if let url = URL(string: appState.appInfo.path.absoluteString) { + let appFolderURL = url.deletingLastPathComponent() // Get the immediate parent directory + + if appFolderURL.path == "/Applications" || appFolderURL.path == "\(home)/Applications" { + // Do nothing, skip insertion + } else if appFolderURL.pathComponents.count > 2 && appFolderURL.path.contains("Applications") { + // Insert into selectedItemsArray only if there is an intermediary folder + selectedItemsArray.insert(appFolderURL, at: 0) + } + } + + + killApp(appId: appState.appInfo.bundleIdentifier) { + moveFilesToTrash(at: selectedItemsArray) { + withAnimation { + showPopover = false + updateOnMain { + appState.isReminderVisible.toggle() } } + refreshAppList(appState.appInfo) } - refreshAppList(appState.appInfo) } + } - + } - + .buttonStyle(NavButtonBottomBarStyle(image: "trash.fill", help: "Uninstall")) + + } else { + Text("No files selected to clean").font(.title).foregroundStyle(Color("mode")).opacity(0.2) + .padding(5) } - .buttonStyle(NavButtonBottomBarStyle(image: "trash.fill", help: "Uninstall")) - .disabled(appState.selectedItems.isEmpty) + Spacer() @@ -265,17 +241,16 @@ struct FilesView: View { .frame(width: 100) .help("Sorting Selection") } - .padding(.top) - + } .transition(.opacity) .padding(20) } - + } } - + func refreshAppList(_ appInfo: AppInfo) { showPopover = false let sortedApps = getSortedApps() @@ -334,16 +309,15 @@ struct FileDetailsItem: View { .lineLimit(2) .truncationMode(.tail) .opacity(0.5) -// .foregroundStyle(Color("AccentColor")) .help(path.path) } - + Spacer() Text(formatByte(size:size!)) - + Button("") { NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) } diff --git a/Pearcleaner/Views/MiniMode.swift b/Pearcleaner/Views/MiniMode.swift index d4f186c..0f16125 100644 --- a/Pearcleaner/Views/MiniMode.swift +++ b/Pearcleaner/Views/MiniMode.swift @@ -34,7 +34,6 @@ struct MiniMode: View { } } - // .padding(.leading, appState.sidebar ? 0 : 10) .transition(.move(edge: .leading)) .popover(isPresented: $showPopover, arrowEdge: .trailing) { VStack { @@ -59,7 +58,7 @@ struct MiniMode: View { } - .frame(minWidth: 300, minHeight: 335) + .frame(minWidth: 300, minHeight: 345) .edgesIgnoringSafeArea(.all) .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) // MARK: Background for whole app @@ -87,26 +86,21 @@ struct MiniEmptyView: View { Spacer() ZStack { - LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing) + LinearGradient(gradient: Gradient(colors: [.green, .orange]), startPoint: .leading, endPoint: .trailing) .mask( Image(systemName: "plus.square.dashed") .resizable() .scaledToFit() - .frame(width: 150, height: 150) + .frame(width: 100, height: 100) .padding() ) } - - -// DropTarget(appState: appState, locations: locations, showPopover: $showPopover) -// .frame(maxWidth: mini ? 300 : 500) - Text("Drop your app here") .font(.title3) .padding(.bottom, 25) .opacity(0.5) - + Spacer() @@ -132,7 +126,6 @@ struct MiniEmptyView: View { } -// .padding() } } @@ -148,6 +141,7 @@ struct MiniAppView: View { @State private var showSys: Bool = true @State private var showUsr: Bool = true @AppStorage("settings.general.mini") private var mini: Bool = false + @AppStorage("settings.general.instant") private var instantSearch: Bool = true @Binding var showPopover: Bool var body: some View { @@ -174,37 +168,21 @@ struct MiniAppView: View { if appState.reload { VStack { Spacer() - ProgressView("Loadings apps and files") + ProgressView(instantSearch ? "Refreshing app list and caching files" : "Refreshing app list") + if instantSearch { + Image(systemName: "bolt.fill") + .resizable() + .scaledToFit() + .foregroundStyle(Color("mode").opacity(0.5)) + .frame(width: 30, height: 30) + .padding() + } Spacer() } .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 { LazyVStack(alignment: .leading, pinnedViews: [.sectionHeaders]) { @@ -253,20 +231,6 @@ struct MiniAppView: View { } } .transition(.move(edge: .leading)) -// .popover(isPresented: $showPopover, arrowEdge: .trailing) { -// VStack { -// FilesView(showPopover: $showPopover) -// .id(appState.appInfo.id) -// } -// .background( -// Rectangle() -// .fill(Color("pop")) -//// .background(Color("pop")) -// .padding(-80) -// ) -// .frame(minWidth: 600, minHeight: 500) -// -// } } } diff --git a/Pearcleaner/Views/TopBar.swift b/Pearcleaner/Views/TopBar.swift index afb533f..a25b218 100644 --- a/Pearcleaner/Views/TopBar.swift +++ b/Pearcleaner/Views/TopBar.swift @@ -9,11 +9,11 @@ import Foundation import SwiftUI struct TopBar: View { - // @Binding var sidebar: Bool @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 + @AppStorage("settings.general.instant") private var instantSearch: Bool = true @EnvironmentObject var appState: AppState @Binding var showPopover: Bool @EnvironmentObject var locations: Locations @@ -50,7 +50,11 @@ struct TopBar: View { appState.currentView = .zombie appState.showProgress.toggle() showPopover.toggle() - reversePathsSearch(appState: appState, locations: locations) + if instantSearch { + reversePathsSearch(appState: appState, locations: locations) + } else { + loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true) + } } else { appState.currentView = .zombie showPopover.toggle() @@ -63,7 +67,6 @@ struct TopBar: View { } - // if appState.currentView != .empty || appState.currentView != .apps { if appState.currentView == .files || appState.currentView == .zombie { Button("") { withAnimation(.easeInOut(duration: 0.5)) { diff --git a/Pearcleaner/Views/TopBarMini.swift b/Pearcleaner/Views/TopBarMini.swift index e9c174e..84004cf 100644 --- a/Pearcleaner/Views/TopBarMini.swift +++ b/Pearcleaner/Views/TopBarMini.swift @@ -12,6 +12,7 @@ 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 + @AppStorage("settings.general.instant") private var instantSearch: Bool = true @Binding var search: String @Binding var showPopover: Bool @EnvironmentObject var appState: AppState @@ -35,7 +36,11 @@ struct TopBarMini: View { appState.currentView = .zombie appState.showProgress.toggle() showPopover.toggle() - reversePathsSearch(appState: appState, locations: locations) + if instantSearch { + reversePathsSearch(appState: appState, locations: locations) + } else { + loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true) + } } else { appState.currentView = .zombie showPopover.toggle() @@ -70,7 +75,11 @@ struct TopBarMini: View { appState.currentView = .zombie appState.showProgress.toggle() showPopover.toggle() - reversePathsSearch(appState: appState, locations: locations) + if instantSearch { + reversePathsSearch(appState: appState, locations: locations) + } else { + loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true) + } } else { appState.currentView = .zombie showPopover.toggle() @@ -108,9 +117,8 @@ struct SearchBarMini: View { HStack { TextField("Search", text: $search) .textFieldStyle(AnimatedSearchStyle(text: $search)) - // .textFieldStyle(SimpleSearchStyle(trash: true, reload: $reload, text: $search)) } - .frame(height: 20) + .frame(height: 20) } } diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index 0f31150..4709dec 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -14,6 +14,7 @@ struct ZombieView: View { @State private var showPop: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false + @AppStorage("settings.general.instant") private var instantSearch: Bool = true @Environment(\.colorScheme) var colorScheme @Binding var showPopover: Bool @Binding var search: String @@ -21,13 +22,11 @@ struct ZombieView: View { var body: some View { VStack(alignment: .center) { - if appState.showProgress { //!self.showDetails { + if appState.showProgress { VStack { Spacer() - // ProgressView("Finding application files..") - // .progressViewStyle(.linear) - // Spacer() - Text("Gathering leftover files, this might take a while..").font(.title3) + + Text("Searching the file system").font(.title3) .foregroundStyle((.gray.opacity(0.8))) ProgressView() .progressViewStyle(.linear) @@ -42,73 +41,44 @@ struct ZombieView: View { // Main Group HStack() { - //icon -// if let appIcon = appState.appInfo.appIcon { -// Image(nsImage: appIcon) -// .resizable() -// .scaledToFit() -// // .aspectRatio(contentMode: .fit) -// .frame(width: 50, height: 50) -// .clipShape(RoundedRectangle(cornerRadius: 8)) -// .padding(.leading) -// } - //app title, size and items + VStack(alignment: .center) { + HStack(alignment: .center) { - VStack(alignment: .leading, spacing: 5){ - HStack(alignment: .center) { + Image(systemName: "clock.arrow.circlepath") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .symbolRenderingMode(.hierarchical) + .padding(.trailing) + + VStack(alignment: .leading, spacing: 10){ + HStack { Text("Leftover Files").font(.title).fontWeight(.bold) - // .foregroundStyle(Color("AccentColor")) -// Text("•").foregroundStyle(Color("AccentColor")) -// Text("\(appState.appInfo.appVersion)").font(.title3) - // .foregroundStyle(.gray.opacity(0.8)) + InfoButton(text: "Leftover file search is not 100% accurate as it doesn't have any app bundles to check against. This searches for files/folders and excludes the ones that have overlap with your currently installed apps. Make sure to confirm files marked for removal are correct.", color: .red) + Spacer() + } -// Text("\(appState.appInfo.bundleIdentifier)").font(.title3) -// .foregroundStyle((.gray.opacity(0.8))) + Text("Files and folders remaining from previously installed applications") + .font(.callout).foregroundStyle((.gray.opacity(0.8))) } Spacer() - Text("\(formatByte(size: appState.zombieFile.totalSize))").font(.title).fontWeight(.bold) - + VStack(alignment: .trailing, spacing: 5) { + Text("\(formatByte(size: appState.zombieFile.totalSize))").font(.title).fontWeight(.bold) + Text("\(appState.zombieFile.fileSize.count == 1 ? "\(appState.zombieFile.fileSize.count) item" : "\(appState.zombieFile.fileSize.count) items")").font(.callout).underline().foregroundStyle((.gray.opacity(0.8))) + } } - - Divider().padding(.bottom, 7) - - HStack{ - Text("Files and folders remaining from previously installed applications") - .font(.callout) - // .foregroundStyle(Color("AccentColor")) - .opacity(0.8) - Spacer() - Text("\(appState.zombieFile.fileSize.count > 1 ? "\(appState.zombieFile.fileSize.count) items" : "\(appState.zombieFile.fileSize.count) item")").font(.callout) -// .foregroundStyle(Color("AccentColor").opacity(0.7)) - .underline() - } - - WarningPopoverView(label: "Caution - Read", - bodyText: "Leftover file search is not 100% accurate as it doesn't have any app bundles to check against.\nThis searches for files/folders and excludes the ones that have overlap with your currently installed apps. \nMake sure to confirm files marked for removal are correct.", - isPresented: $showPop) - .padding(.vertical, 5) - - } - .padding(20) + .padding(.horizontal, 20) + .padding(.top, 10) } - // .padding(.horizontal) - .background( - RoundedRectangle(cornerRadius: 8) - // .strokeBorder(Color("AccentColor"), lineWidth: 0.5) - .fill(Color("mode").opacity(colorScheme == .dark ? 0.05 : 0.05)) - // .background( - // RoundedRectangle(cornerRadius: 8) - // .strokeBorder(Color("AccentColor").opacity(colorScheme == .dark ? 0.1 : 0.1), lineWidth: 1) - // ) - ) - + Divider() + .padding() ScrollView() { LazyVStack { @@ -170,51 +140,62 @@ struct ZombieView: View { } - Button("Rescan") { - updateOnMain { - appState.zombieFile = .empty - appState.showProgress.toggle() - reversePathsSearch(appState: appState, locations: locations) - } - } - .buttonStyle(NavButtonBottomBarStyle(image: "arrow.counterclockwise.circle.fill", help: "Rescan files")) - Button("Remove") { - Task { - updateOnMain { - appState.zombieFile = .empty - search = "" - if mini { - appState.currentView = .apps - showPopover = false - } else { - appState.currentView = .empty - } - } - let selectedItemsArray = Array(appState.selectedZombieItems) - .filter { !$0.path.contains(".Trash") } - - killApp(appId: appState.appInfo.bundleIdentifier) { - moveFilesToTrash(at: selectedItemsArray) { - withAnimation { + if !appState.selectedZombieItems.isEmpty { + Button("Remove") { + Task { + updateOnMain { + appState.zombieFile = .empty + search = "" + if mini { + appState.currentView = .apps showPopover = false - updateOnMain { - appState.isReminderVisible.toggle() - if sentinel { - launchctl(load: true) + } else { + appState.currentView = .empty + } + } + + let selectedItemsArray = Array(appState.selectedZombieItems) + .filter { !$0.path.contains(".Trash") } + + killApp(appId: appState.appInfo.bundleIdentifier) { + moveFilesToTrash(at: selectedItemsArray) { + withAnimation { + showPopover = false + updateOnMain { + appState.isReminderVisible.toggle() + if sentinel { + launchctl(load: true) + } } } } } + } } - + .buttonStyle(NavButtonBottomBarStyle(image: "trash.fill", help: "Remove")) + } else { + Text("No files selected to remove").font(.title).foregroundStyle(Color("mode")).opacity(0.2) + .padding(5) } - .buttonStyle(NavButtonBottomBarStyle(image: "trash.fill", help: "Remove")) - .disabled(appState.selectedZombieItems.isEmpty) + + Button("Rescan") { + updateOnMain { + appState.zombieFile = .empty + appState.showProgress.toggle() + if instantSearch { + reversePathsSearch(appState: appState, locations: locations) + } else { + loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true) + } + } + } + .buttonStyle(NavButtonBottomBarStyle(image: "arrow.counterclockwise.circle.fill", help: "Rescan files")) + Spacer() diff --git a/Pearcleaner/Windows/NewWin.swift b/Pearcleaner/Windows/NewWin.swift index e306fde..820a3be 100644 --- a/Pearcleaner/Windows/NewWin.swift +++ b/Pearcleaner/Windows/NewWin.swift @@ -78,10 +78,6 @@ class NewWin: NSWindowController { return AnyView(PermView() .frame(maxWidth: .infinity, maxHeight: .infinity) .environmentObject(appState)) - // case .restart: - // return AnyView(RestartView() - // .frame(maxWidth: .infinity, maxHeight: .infinity) - // .environmentObject(appState)) } } } diff --git a/Pearcleaner/Windows/PermView.swift b/Pearcleaner/Windows/PermView.swift index 523e117..f31e544 100644 --- a/Pearcleaner/Windows/PermView.swift +++ b/Pearcleaner/Windows/PermView.swift @@ -30,11 +30,8 @@ struct PermView: View { Text("Pearcleaner requires the following permissions:") .font(.title3) .fontWeight(.semibold) - // .padding(.horizontal, 20) .padding(.vertical) - // .padding(.bottom) - - + ScrollView { Text(" - Full Disk Access permission to find and delete files in non-user locations.\n - Accessibility permission to delete files via Finder which allows for the Undo function.\n\nAdd Pearcleaner in both Privacy panes via the + or by dragging the app over the pane. If the app is already pre-populated in the list, just toggle On. Restart app when both permissions are granted.") .font(.body) @@ -69,7 +66,6 @@ struct PermView: View { .padding() Button(action: { -// NewWin.close relaunchApp(afterDelay: 1) }) { Text("Restart")