This commit is contained in:
Alin
2024-03-01 18:28:21 -07:00
parent e87bbe0c9f
commit c3ce997a88
37 changed files with 783 additions and 2211 deletions
+8 -20
View File
@@ -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 = "<group>"; };
C76D08472AF83C3F00D07867 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = "<group>"; };
C76D08492AF83C6E00D07867 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = "<group>"; };
C76D084B2AF83C9D00D07867 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; };
C76D08502AF850F300D07867 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
C76D08542AF89CDE00D07867 /* AppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.alienator88.PearcleanerSentinel.plist; sourceTree = "<group>"; };
C7A9CE462B89164700EB6E78 /* Authorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = "<group>"; };
C7ACADE52B92A737000B5845 /* Features.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features.swift; sourceTree = "<group>"; };
C7CF47232B3B3F1700979C5F /* WindowSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowSettings.swift; sourceTree = "<group>"; };
C7D31D452AFDA90C00C7ED9E /* Sentinel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sentinel.swift; sourceTree = "<group>"; };
C7D31D472AFEB23700C7ED9E /* TopBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBar.swift; sourceTree = "<group>"; };
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = "<group>"; };
C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = "<group>"; };
C7D31D542B008CB900C7ED9E /* Shapes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shapes.swift; sourceTree = "<group>"; };
C7D31D582B00A49300C7ED9E /* DropTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropTarget.swift; sourceTree = "<group>"; };
C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
@@ -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;
+2 -6
View File
@@ -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)
}
-76
View File
@@ -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
//]
-69
View File
@@ -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
// }
// }
}
}
+49
View File
@@ -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<Bool>, features: Binding<String>) {
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()
}
+126 -253
View File
@@ -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<Bool>) {
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()
//}
-599
View File
@@ -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)
//// )
////}
+164 -150
View File
@@ -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<Double>) {
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)
+10 -83
View File
@@ -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"])
+27 -13
View File
@@ -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 {
@@ -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"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 KiB

@@ -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
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

@@ -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
}
}
@@ -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
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

+2 -1
View File
@@ -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")
+28 -17
View File
@@ -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"
// }
}
}
-150
View File
@@ -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)
}
}
}
-56
View File
@@ -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)
}
}
+3 -13
View File
@@ -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 {
+4 -20
View File
@@ -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)")
+12 -33
View File
@@ -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)
//
// }
}
}
+101 -87
View File
@@ -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)
}
}
-231
View File
@@ -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<Bool>) {
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
}
}
}
+104 -130
View File
@@ -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)
}
+14 -50
View File
@@ -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)
//
// }
}
}
+6 -3
View File
@@ -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)) {
+12 -4
View File
@@ -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)
}
}
+72 -91
View File
@@ -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()
-4
View File
@@ -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))
}
}
}
+1 -5
View File
@@ -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")