This commit is contained in:
Alin
2024-09-20 15:15:53 -06:00
parent ef23d0a724
commit 8eeea2b9f8
19 changed files with 235 additions and 1214 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon_32x32.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+5 -78
View File
@@ -27,8 +27,11 @@ class FinderOpen: FIFinderSync {
selectedItemURLs.count == 1, selectedItemURLs.first?.pathExtension == "app" { selectedItemURLs.count == 1, selectedItemURLs.first?.pathExtension == "app" {
// Add menu item if the selected item is a .app file // Add menu item if the selected item is a .app file
let menuItem = NSMenuItem(title: "Pearcleaner Uninstall", action: #selector(openInMyApp), keyEquivalent: "") let menuItem = NSMenuItem(title: "Pearcleaner Uninstall", action: #selector(openInMyApp), keyEquivalent: "")
// menuItem.image = NSImage(named: NSImage.trashEmptyName) if let appIcon = NSImage(named: "Icon") {
// menu.addItem(withTitle: "Pearcleaner Uninstall", action: #selector(openInMyApp), keyEquivalent: "") menuItem.image = appIcon
} else {
menuItem.image = NSImage(named: NSImage.trashFullName)
}
menu.addItem(menuItem) menu.addItem(menuItem)
} }
} }
@@ -53,79 +56,3 @@ class FinderOpen: FIFinderSync {
} }
//class FinderSync: FIFinderSync {
//
// var myFolderURL = URL(fileURLWithPath: "/Users/Shared/MySyncExtension Documents")
//
// override init() {
// super.init()
//
// NSLog("FinderSync() launched from %@", Bundle.main.bundlePath as NSString)
//
// // Set up the directory we are syncing.
// FIFinderSyncController.default().directoryURLs = [self.myFolderURL]
//
// // Set up images for our badge identifiers. For demonstration purposes, this uses off-the-shelf images.
// FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.colorPanelName)!, label: "Status One" , forBadgeIdentifier: "One")
// FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.cautionName)!, label: "Status Two", forBadgeIdentifier: "Two")
// }
//
// // MARK: - Primary Finder Sync protocol methods
//
// override func beginObservingDirectory(at url: URL) {
// // The user is now seeing the container's contents.
// // If they see it in more than one view at a time, we're only told once.
// NSLog("beginObservingDirectoryAtURL: %@", url.path as NSString)
// }
//
//
// override func endObservingDirectory(at url: URL) {
// // The user is no longer seeing the container's contents.
// NSLog("endObservingDirectoryAtURL: %@", url.path as NSString)
// }
//
// override func requestBadgeIdentifier(for url: URL) {
// NSLog("requestBadgeIdentifierForURL: %@", url.path as NSString)
//
// // For demonstration purposes, this picks one of our two badges, or no badge at all, based on the filename.
// let whichBadge = abs(url.path.hash) % 3
// let badgeIdentifier = ["", "One", "Two"][whichBadge]
// FIFinderSyncController.default().setBadgeIdentifier(badgeIdentifier, for: url)
// }
//
// // MARK: - Menu and toolbar item support
//
// override var toolbarItemName: String {
// return "FinderSy"
// }
//
// override var toolbarItemToolTip: String {
// return "FinderSy: Click the toolbar item for a menu."
// }
//
// override var toolbarItemImage: NSImage {
// return NSImage(named: NSImage.cautionName)!
// }
//
// override func menu(for menuKind: FIMenuKind) -> NSMenu {
// // Produce a menu for the extension.
// let menu = NSMenu(title: "")
// menu.addItem(withTitle: "Example Menu Item", action: #selector(sampleAction(_:)), keyEquivalent: "")
// return menu
// }
//
// @IBAction func sampleAction(_ sender: AnyObject?) {
// let target = FIFinderSyncController.default().targetedURL()
// let items = FIFinderSyncController.default().selectedItemURLs()
//
// let item = sender as! NSMenuItem
// NSLog("sampleAction: menu item: %@, target = %@, items = ", item.title as NSString, target!.path as NSString)
// for obj in items! {
// NSLog(" %@", obj.path as NSString)
// }
// }
//
//}
+5 -33
View File
@@ -11,13 +11,7 @@
C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71848432B8D2D600046CB13 /* ZombieView.swift */; }; C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71848432B8D2D600046CB13 /* ZombieView.swift */; };
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; }; C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; };
C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; }; C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; };
C72F4B6C2C5DF4ED00B04F60 /* SunburstChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72F4B6B2C5DF4ED00B04F60 /* SunburstChart.swift */; };
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */; }; C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */; };
C74D76B72C5C4ABC00748DD1 /* TreeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */; };
C74D76BD2C5D2DF300748DD1 /* FileSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74D76B92C5D2DF300748DD1 /* FileSearch.swift */; };
C74D76BE2C5D2DF300748DD1 /* PearDiskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74D76BA2C5D2DF300748DD1 /* PearDiskView.swift */; };
C74D76BF2C5D2DF300748DD1 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74D76BB2C5D2DF300748DD1 /* Data.swift */; };
C74D76C02C5D2DF300748DD1 /* FolderDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74D76BC2C5D2DF300748DD1 /* FolderDetail.swift */; };
C74FDC632BCD833D00B8960F /* Conditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74FDC622BCD833D00B8960F /* Conditions.swift */; }; C74FDC632BCD833D00B8960F /* Conditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74FDC622BCD833D00B8960F /* Conditions.swift */; };
C7575D392B0182DE006A600A /* PearcleanerSentinel in CopyFiles */ = {isa = PBXBuildFile; fileRef = C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; C7575D392B0182DE006A600A /* PearcleanerSentinel in CopyFiles */ = {isa = PBXBuildFile; fileRef = C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; }; C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; };
@@ -39,6 +33,7 @@
C79947962C34A794007279CC /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79947952C34A794007279CC /* Tips.swift */; }; C79947962C34A794007279CC /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79947952C34A794007279CC /* Tips.swift */; };
C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */; }; C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */; };
C7A2AE5D2B9644F300161540 /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7A2AE5C2B9644F300161540 /* FileWatcher */; }; C7A2AE5D2B9644F300161540 /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7A2AE5C2B9644F300161540 /* FileWatcher */; };
C7A6DBF12C9DD27200CFA042 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C7A6DBF02C9DD27200CFA042 /* Assets.xcassets */; };
C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CF47232B3B3F1700979C5F /* WindowSettings.swift */; }; C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CF47232B3B3F1700979C5F /* WindowSettings.swift */; };
C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.swift */; }; C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.swift */; };
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D502AFF00F300C7ED9E /* Locations.swift */; }; C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D502AFF00F300C7ED9E /* Locations.swift */; };
@@ -102,13 +97,7 @@
C72893042AFD42E600C8C1CD /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; }; C72893042AFD42E600C8C1CD /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PearcleanerSentinel; sourceTree = BUILT_PRODUCTS_DIR; }; C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PearcleanerSentinel; sourceTree = BUILT_PRODUCTS_DIR; };
C72893112AFD51EA00C8C1CD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; }; C72893112AFD51EA00C8C1CD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
C72F4B6B2C5DF4ED00B04F60 /* SunburstChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SunburstChart.swift; sourceTree = "<group>"; };
C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathsFetch.swift; sourceTree = "<group>"; }; C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathsFetch.swift; sourceTree = "<group>"; };
C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeMap.swift; sourceTree = "<group>"; };
C74D76B92C5D2DF300748DD1 /* FileSearch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileSearch.swift; sourceTree = "<group>"; };
C74D76BA2C5D2DF300748DD1 /* PearDiskView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PearDiskView.swift; sourceTree = "<group>"; };
C74D76BB2C5D2DF300748DD1 /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
C74D76BC2C5D2DF300748DD1 /* FolderDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderDetail.swift; sourceTree = "<group>"; };
C74FDC622BCD833D00B8960F /* Conditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conditions.swift; sourceTree = "<group>"; }; C74FDC622BCD833D00B8960F /* Conditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conditions.swift; sourceTree = "<group>"; };
C76D08472AF83C3F00D07867 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.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>"; }; C76D08492AF83C6E00D07867 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = "<group>"; };
@@ -133,6 +122,7 @@
C79947952C34A794007279CC /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = "<group>"; }; C79947952C34A794007279CC /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = "<group>"; };
C7A27E7F2AFD70DD00166168 /* PearcleanerSentinel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PearcleanerSentinel.entitlements; sourceTree = "<group>"; }; 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>"; }; C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.alienator88.PearcleanerSentinel.plist; sourceTree = "<group>"; };
C7A6DBF02C9DD27200CFA042 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C7CF47232B3B3F1700979C5F /* WindowSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowSettings.swift; sourceTree = "<group>"; }; C7CF47232B3B3F1700979C5F /* WindowSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowSettings.swift; sourceTree = "<group>"; };
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.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>"; }; C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = "<group>"; };
@@ -211,19 +201,6 @@
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
C74D76B82C5D2D7900748DD1 /* PearDisk */ = {
isa = PBXGroup;
children = (
C74D76BA2C5D2DF300748DD1 /* PearDiskView.swift */,
C74D76BB2C5D2DF300748DD1 /* Data.swift */,
C74D76B92C5D2DF300748DD1 /* FileSearch.swift */,
C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */,
C74D76BC2C5D2DF300748DD1 /* FolderDetail.swift */,
C72F4B6B2C5DF4ED00B04F60 /* SunburstChart.swift */,
);
path = PearDisk;
sourceTree = "<group>";
};
C76D08462AF83C2F00D07867 /* Settings */ = { C76D08462AF83C2F00D07867 /* Settings */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -243,8 +220,8 @@
children = ( children = (
C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */, C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */,
C77B90142AF18E7C009CC655 /* Info.plist */, C77B90142AF18E7C009CC655 /* Info.plist */,
C77B90072AF18E2F009CC655 /* Assets.xcassets */,
C77B90242AF2D796009CC655 /* Pearcleaner.entitlements */, C77B90242AF2D796009CC655 /* Pearcleaner.entitlements */,
C77B90072AF18E2F009CC655 /* Assets.xcassets */,
); );
path = Resources; path = Resources;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -277,7 +254,6 @@
C786F1A72C29E599001B3800 /* Builds */, C786F1A72C29E599001B3800 /* Builds */,
C76D084F2AF84E0100D07867 /* Resources */, C76D084F2AF84E0100D07867 /* Resources */,
C76D08462AF83C2F00D07867 /* Settings */, C76D08462AF83C2F00D07867 /* Settings */,
C74D76B82C5D2D7900748DD1 /* PearDisk */,
C77B901F2AF1B390009CC655 /* Views */, C77B901F2AF1B390009CC655 /* Views */,
C72893072AFD442600C8C1CD /* Logic */, C72893072AFD442600C8C1CD /* Logic */,
); );
@@ -305,6 +281,7 @@
C78121652BC892A000BE06BD /* FinderOpen.swift */, C78121652BC892A000BE06BD /* FinderOpen.swift */,
C78121672BC892A000BE06BD /* Info.plist */, C78121672BC892A000BE06BD /* Info.plist */,
C78121682BC892A000BE06BD /* FinderOpen.entitlements */, C78121682BC892A000BE06BD /* FinderOpen.entitlements */,
C7A6DBF02C9DD27200CFA042 /* Assets.xcassets */,
); );
path = FinderOpen; path = FinderOpen;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -432,6 +409,7 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C7A6DBF12C9DD27200CFA042 /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -451,21 +429,16 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C7DD49EA2BAB4D8100CCBA16 /* AppInfoFetch.swift in Sources */, C7DD49EA2BAB4D8100CCBA16 /* AppInfoFetch.swift in Sources */,
C74D76BD2C5D2DF300748DD1 /* FileSearch.swift in Sources */,
C79947962C34A794007279CC /* Tips.swift in Sources */, C79947962C34A794007279CC /* Tips.swift in Sources */,
C76D08482AF83C3F00D07867 /* Update.swift in Sources */, C76D08482AF83C3F00D07867 /* Update.swift in Sources */,
C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */, C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */,
C77B90162AF19377009CC655 /* AppState.swift in Sources */, C77B90162AF19377009CC655 /* AppState.swift in Sources */,
C72F4B6C2C5DF4ED00B04F60 /* SunburstChart.swift in Sources */,
C74D76B72C5C4ABC00748DD1 /* TreeMap.swift in Sources */,
C74D76C02C5D2DF300748DD1 /* FolderDetail.swift in Sources */,
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */, C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */,
C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */, C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */,
C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */, C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */,
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */, C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */,
C77B901A2AF1938F009CC655 /* Logic.swift in Sources */, C77B901A2AF1938F009CC655 /* Logic.swift in Sources */,
C77B901C2AF193A3009CC655 /* Styles.swift in Sources */, C77B901C2AF193A3009CC655 /* Styles.swift in Sources */,
C74D76BF2C5D2DF300748DD1 /* Data.swift in Sources */,
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */, C7045A282B03E71D00376976 /* MiniMode.swift in Sources */,
C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */, C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */,
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */, C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */,
@@ -475,7 +448,6 @@
C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */, C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */,
C76D08512AF850F300D07867 /* About.swift in Sources */, C76D08512AF850F300D07867 /* About.swift in Sources */,
C77B90182AF19382009CC655 /* AppCommands.swift in Sources */, C77B90182AF19382009CC655 /* AppCommands.swift in Sources */,
C74D76BE2C5D2DF300748DD1 /* PearDiskView.swift in Sources */,
C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */, C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */,
C77B901E2AF1A9DB009CC655 /* SettingsWindow.swift in Sources */, C77B901E2AF1A9DB009CC655 /* SettingsWindow.swift in Sources */,
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */, C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */,
+22 -4
View File
@@ -7,7 +7,7 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
import FinderSync //import FinderSync
let home = FileManager.default.homeDirectoryForCurrentUser.path let home = FileManager.default.homeDirectoryForCurrentUser.path
@@ -79,10 +79,28 @@ class AppState: ObservableObject {
} }
@objc func updateExtensionStatus() { @objc func updateExtensionStatus() {
let extensionStatus = FIFinderSyncController.isExtensionEnabled let task = Process()
DispatchQueue.main.async { let pipe = Pipe()
self.finderExtensionEnabled = extensionStatus
task.launchPath = "/bin/zsh"
task.arguments = ["-c", "pluginkit -m -i com.alienator88.Pearcleaner"]
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
task.waitUntilExit()
if let output = String(data: data, encoding: .utf8) {
// Check if the output starts with a '+' indicating it's enabled
let extensionStatus = output.contains("+")
DispatchQueue.main.async {
self.finderExtensionEnabled = extensionStatus
}
} }
// let extensionStatus = FIFinderSyncController.isExtensionEnabled
// DispatchQueue.main.async {
// self.finderExtensionEnabled = extensionStatus
// }
} }
func triggerUninstallAlert() { func triggerUninstallAlert() {
+14
View File
@@ -27,12 +27,14 @@ func reloadAppsList(appState: AppState, fsm: FolderSettingsManager) {
func resizeWindowAuto(windowSettings: WindowSettings, title: String) { func resizeWindowAuto(windowSettings: WindowSettings, title: String) {
if let window = NSApplication.shared.windows.first(where: { $0.title == title }) { if let window = NSApplication.shared.windows.first(where: { $0.title == title }) {
print(windowSettings.loadWindowSettings())
let newSize = NSSize(width: windowSettings.loadWindowSettings().width, height: windowSettings.loadWindowSettings().height) let newSize = NSSize(width: windowSettings.loadWindowSettings().width, height: windowSettings.loadWindowSettings().height)
window.setContentSize(newSize) window.setContentSize(newSize)
} }
} }
// Check if Pearcleaner has any windows open // Check if Pearcleaner has any windows open
func hasWindowOpen() -> Bool { func hasWindowOpen() -> Bool {
for window in NSApp.windows where window.title == "Pearcleaner" { for window in NSApp.windows where window.title == "Pearcleaner" {
@@ -260,6 +262,18 @@ func manageSymlink(install: Bool) {
} }
// FinderExtension Sequoia Fix
func manageFinderPlugin(install: Bool) {
let task = Process()
task.launchPath = "/usr/bin/pluginkit"
task.arguments = ["-e", "\(install ? "use" : "ignore")", "-i", "com.alienator88.Pearcleaner"]
task.launch()
task.waitUntilExit()
}
// Brew cleanup // Brew cleanup
func caskCleanup(app: String) { func caskCleanup(app: String) {
+31 -30
View File
@@ -13,28 +13,20 @@ import Combine
class WindowSettings { class WindowSettings {
private let windowWidthKey = "windowWidthKey" private let windowWidthKey = "windowWidthKey"
private let windowHeightKey = "windowHeightKey" private let windowHeightKey = "windowHeightKey"
private let windowWidthKeyMini = "windowWidthKeyMini"
private let windowHeightKeyMini = "windowHeightKeyMini"
private let windowXKey = "windowXKey" private let windowXKey = "windowXKey"
private let windowYKey = "windowYKey" private let windowYKey = "windowYKey"
@AppStorage("settings.general.mini") private var mini: Bool = false
var windows: [NSWindow] = [] var windows: [NSWindow] = []
// Register default sizes if the AppStorage keys are invalid
func registerDefaultWindowSettings(completion: @escaping () -> Void = {}) { func registerDefaultWindowSettings(completion: @escaping () -> Void = {}) {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
// Get primary screen // Get primary screen
let screenFrame = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 800, height: 600) let screenFrame = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 800, height: 600)
// Calculate default window sizes and x/y coordinates // Calculate default window sizes and x/y coordinates
let defaultWidth = Float(900) // Default width for regular window let defaultWidth = Float(900) // Default width for regular window
let defaultHeight = Float(600) // Default height for regular window let defaultHeight = Float(628) // Default height for regular window
let defaultWidthMini = Float(300) // Default width for mini window
let defaultHeightMini = Float(370) // Default height for mini window
let defaultX = Float((screenFrame.width - CGFloat(defaultWidth)) / 2 + screenFrame.origin.x) // Default X coordinate let defaultX = Float((screenFrame.width - CGFloat(defaultWidth)) / 2 + screenFrame.origin.x) // Default X coordinate
let defaultY = Float((screenFrame.height - CGFloat(defaultHeight)) / 2 + screenFrame.origin.y) // Default Y coordinate let defaultY = Float((screenFrame.height - CGFloat(defaultHeight)) / 2 + screenFrame.origin.y) // Default Y coordinate
// Set defaults only if they are not already set // Set defaults only if they are not already set
if defaults.object(forKey: windowWidthKey) == nil { if defaults.object(forKey: windowWidthKey) == nil {
defaults.set(defaultWidth, forKey: windowWidthKey) defaults.set(defaultWidth, forKey: windowWidthKey)
@@ -42,60 +34,69 @@ class WindowSettings {
if defaults.object(forKey: windowHeightKey) == nil { if defaults.object(forKey: windowHeightKey) == nil {
defaults.set(defaultHeight, forKey: windowHeightKey) defaults.set(defaultHeight, forKey: windowHeightKey)
} }
if defaults.object(forKey: windowWidthKeyMini) == nil {
defaults.set(defaultWidthMini, forKey: windowWidthKeyMini)
}
if defaults.object(forKey: windowHeightKeyMini) == nil {
defaults.set(defaultHeightMini, forKey: windowHeightKeyMini)
}
if defaults.object(forKey: windowXKey) == nil { if defaults.object(forKey: windowXKey) == nil {
defaults.set(defaultX, forKey: windowXKey) defaults.set(defaultX, forKey: windowXKey)
} }
if defaults.object(forKey: windowYKey) == nil { if defaults.object(forKey: windowYKey) == nil {
defaults.set(defaultY, forKey: windowYKey) defaults.set(defaultY, forKey: windowYKey)
} }
completion() completion()
} }
// Save user window settings // Save user window settings
func saveWindowSettings(frame: NSRect) { func saveWindowSettings(frame: NSRect) {
UserDefaults.standard.set(Float(frame.size.width), forKey: mini ? windowWidthKeyMini : windowWidthKey) UserDefaults.standard.set(Float(frame.size.width), forKey: windowWidthKey)
UserDefaults.standard.set(Float(frame.size.height), forKey: mini ? windowHeightKeyMini : windowHeightKey) UserDefaults.standard.set(Float(frame.size.height), forKey: windowHeightKey)
UserDefaults.standard.set(Float(frame.origin.x), forKey: windowXKey) UserDefaults.standard.set(Float(frame.origin.x), forKey: windowXKey)
UserDefaults.standard.set(Float(frame.origin.y), forKey: windowYKey) UserDefaults.standard.set(Float(frame.origin.y), forKey: windowYKey)
} }
// Load default window settings or user defined settings // Load default window settings or user defined settings
func loadWindowSettings() -> NSRect { func loadWindowSettings() -> NSRect {
let width = CGFloat(UserDefaults.standard.float(forKey: mini ? windowWidthKeyMini : windowWidthKey)) let width = CGFloat(UserDefaults.standard.float(forKey: windowWidthKey))
let height = CGFloat(UserDefaults.standard.float(forKey: mini ? windowHeightKeyMini : windowHeightKey)) let height = CGFloat(UserDefaults.standard.float(forKey: windowHeightKey))
let x = CGFloat(UserDefaults.standard.float(forKey: windowXKey)) let x = CGFloat(UserDefaults.standard.float(forKey: windowXKey))
let y = CGFloat(UserDefaults.standard.float(forKey: windowYKey)) let y = CGFloat(UserDefaults.standard.float(forKey: windowYKey))
return NSRect(x: x, y: y, width: width, height: height) return NSRect(x: x, y: y, width: width, height: height)
} }
// Reset to default sizes when toggling the window state
func resetWindowSettings(mini: Bool) -> NSRect {
let screenFrame = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 800, height: 600)
// Define sizes based on whether mini mode is active
let defaultWidth: CGFloat = mini ? 300 : 900 // Mini or Regular width
let defaultHeight: CGFloat = mini ? 373 : 628 // Mini or Regular height
// Calculate the X and Y coordinates based on the size
let defaultX = (screenFrame.width - defaultWidth) / 2 + screenFrame.origin.x
let defaultY = (screenFrame.height - defaultHeight) / 2 + screenFrame.origin.y
// Store these values in UserDefaults (if needed)
UserDefaults.standard.set(Float(defaultWidth), forKey: windowWidthKey)
UserDefaults.standard.set(Float(defaultHeight), forKey: windowHeightKey)
UserDefaults.standard.set(Float(defaultX), forKey: windowXKey)
UserDefaults.standard.set(Float(defaultY), forKey: windowYKey)
// Return the frame for the window with appropriate size and position
return NSRect(x: defaultX, y: defaultY, width: defaultWidth, height: defaultHeight)
}
// Launch new app windows on demand // Launch new app windows on demand
func newWindow<V: View>(withView view: @escaping () -> V) { func newWindow<V: View>(mini: Bool, withView view: @escaping () -> V) {
// Close existing window
findAndHideWindows(named: ["Pearcleaner"]) findAndHideWindows(named: ["Pearcleaner"])
// Create new window using defaults
let contentView = view let contentView = view
let frame = self.loadWindowSettings() let frame = self.resetWindowSettings(mini: mini)
let newWindow = NSWindow( let newWindow = NSWindow(
contentRect: frame, contentRect: frame,
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false) backing: .buffered, defer: false)
newWindow.contentView = NSHostingView(rootView: contentView())
newWindow.titlebarAppearsTransparent = true newWindow.titlebarAppearsTransparent = true
newWindow.isMovableByWindowBackground = true newWindow.isMovableByWindowBackground = true
newWindow.center()
newWindow.title = "Pearcleaner" newWindow.title = "Pearcleaner"
newWindow.isRestorable = false newWindow.isRestorable = false
newWindow.titleVisibility = .hidden
newWindow.setFrameAutosaveName("Pearcleaner")
newWindow.contentView = NSHostingView(rootView: contentView())
self.windows.append(newWindow) self.windows.append(newWindow)
newWindow.titleVisibility = .hidden
newWindow.makeKeyAndOrderFront(nil) newWindow.makeKeyAndOrderFront(nil)
resizeWindowAuto(windowSettings: self, title: "Pearcleaner")
} }
} }
-184
View File
@@ -1,184 +0,0 @@
////
//// Item.swift
//// PearDisk
////
//// Created by Alin Lupascu on 7/17/24.
////
//
//import Foundation
//import SwiftData
//
//// Data Model
//@Model
//final class Item: Identifiable {
// @Attribute(.unique) let url: URL
// let name: String
// var size: Int64
// let isDirectory: Bool
// let parentURL: URL?
// let timestamp: Date
//
// init(url: URL, name: String, size: Int64, isDirectory: Bool, parentURL: URL? = nil) {
// self.url = url
// self.name = name
// self.size = size
// self.isDirectory = isDirectory
// self.parentURL = parentURL
// self.timestamp = Date()
// }
//}
//
//
//// Wipe peardisk.sqlite file
//func wipeSwiftDataStorage() async {
// let fileManager = FileManager.default
// guard let urlApp = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
// print("Could not find the Application Support directory.")
// return
// }
//
// let peardiskURL = urlApp.appendingPathComponent("PearDisk").appendingPathComponent("Database")
//
// do {
//
// if fileManager.fileExists(atPath: peardiskURL.path) {
// try fileManager.removeItem(at: peardiskURL)
// print("\(peardiskURL.lastPathComponent) has been deleted.")
// }
// } catch {
// print("Error deleting SwiftData storage: \(error)")
// }
//}
//
//func wipeSwiftDataStorage2() async {
// guard let container = try? ModelContainer(for: Item.self) else {
// print("Failed to create ModelContainer.")
// return
// }
//
// let urlApp = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
//
// if let urlApp = urlApp {
// let url = urlApp.appendingPathComponent("PearDisk").appendingPathComponent("peardisk.sqlite")
// if FileManager.default.fileExists(atPath: url.path) {
// print("Peardisk.sqlite: \(url.path(percentEncoded: false))")
// }
// } else {
// fatalError("Could not find the Application Support directory.")
// }
//
//
// await MainActor.run {
// let context = container.mainContext
//
// do {
// try context.delete(model: Item.self)
// try context.save()
// print("Peardisk.sqlite has been successfully wiped.")
// } catch {
// print("Error wiping SwiftData storage: \(error)")
// }
// }
//}
//
//
//
//
//// Print size of peardisk.sqlite file
//func printSwiftDataCacheSize() async {
// // Get the Application Support directory URL for your app
// guard let applicationSupportDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
// print("Could not find the Application Support directory.")
// return
// }
//
// // Create the custom directory URL
// let customDirectoryURL = applicationSupportDirectory.appendingPathComponent("PearDisk").appendingPathComponent("Database")
// let storeURL = customDirectoryURL.appendingPathComponent("peardisk").appendingPathExtension("sqlite")
//
// // Check if the store URL exists
// guard FileManager.default.fileExists(atPath: storeURL.path) else {
// print("Store file does not exist.")
// return
// }
//
// // Initialize the ModelContainer with the custom store URL
// let modelConfiguration = ModelConfiguration(url: storeURL)
//
// do {
// let container = try ModelContainer(for: Item.self, configurations: modelConfiguration)
//
// await MainActor.run {
// let context = container.mainContext
// let fetchDescriptor = FetchDescriptor<Item>()
//
// do {
// let count = try context.fetchCount(fetchDescriptor)
// print("Number of Item objects: \(count)")
//
// // Get the size of the store file directly
// let fileManager = FileManager.default
// do {
// let attributes = try fileManager.attributesOfItem(atPath: storeURL.path)
// if let size = attributes[.size] as? Int64 {
// let sizeMB = Double(size) / (1024 * 1024)
// print("Total size of SwiftData storage: \(String(format: "%.2f", sizeMB)) MB")
// } else {
// print("Could not retrieve file size.")
// }
// } catch {
// print("Could not access file at \(storeURL.path): \(error)")
// }
// } catch {
// print("Error fetching data: \(error)")
// }
// }
// } catch {
// print("Could not create ModelContainer: \(error)")
// }
//}
//
//
//
//// Setup Data Model
//func createSharedModelContainer() -> ModelContainer {
// do {
// // Get the Application Support directory URL for your app
// guard let applicationSupportDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
// fatalError("Could not find the Application Support directory.")
// }
//
// // Create the custom directory URL
// let customDirectoryURL = applicationSupportDirectory.appendingPathComponent("PearDisk", isDirectory: true).appendingPathComponent("Database", isDirectory: true)
//
// // Create the directory if it doesn't exist
// do {
// try FileManager.default.createDirectory(at: customDirectoryURL, withIntermediateDirectories: true, attributes: nil)
// } catch {
// fatalError("Could not create directory: \(error)")
// }
//
// // Create the store URL with the desired filename inside the custom directory
// let storeURL = customDirectoryURL.appendingPathComponent("peardisk").appendingPathExtension("sqlite")
//
// // Ensure the directory is accessible for reading and writing
// let fileManager = FileManager.default
// var isDirectory: ObjCBool = true
// if !fileManager.fileExists(atPath: customDirectoryURL.path, isDirectory: &isDirectory) {
// fatalError("Directory does not exist.")
// }
//
// if !fileManager.isWritableFile(atPath: customDirectoryURL.path) {
// fatalError("Directory is not writable.")
// }
//
// if !fileManager.isReadableFile(atPath: customDirectoryURL.path) {
// fatalError("Directory is not readable.")
// }
//
// let modelConfiguration = ModelConfiguration(url: storeURL)
// return try ModelContainer(for: Item.self, configurations: modelConfiguration)
// } catch {
// fatalError("Could not create ModelContainer: \(error)")
// }
//}
-87
View File
@@ -1,87 +0,0 @@
//
// FileSearch.swift
// Pearcleaner
//
// Created by Alin Lupascu on 7/20/24.
//
import Foundation
import SwiftData
import AlinFoundation
enum FastFileSearchError: Error {
case invalidPath
case failedToReadDirectory
case failedToGetFileInfo
}
class FastFileSearch {
private let queue = DispatchQueue(label: "com.alienator88.Pearcleaner.disk", qos: .userInitiated, attributes: .concurrent)
func search(url: URL, completion: @escaping (Result<[Item], Error>) -> Void) {
queue.async {
do {
let items = try self.searchRecursive(url: url)
let sortedItems = items.sorted { $0.size > $1.size }
DispatchQueue.main.async {
completion(.success(sortedItems))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
private func searchRecursive(url: URL) throws -> [Item] {
var items: [Item] = []
guard let path = url.path.cString(using: .utf8) else {
throw FastFileSearchError.invalidPath
}
guard let dir = opendir(path) else {
throw FastFileSearchError.failedToReadDirectory
}
defer { closedir(dir) }
while let entry = readdir(dir) {
let name = withUnsafePointer(to: entry.pointee.d_name) { ptr in
ptr.withMemoryRebound(to: CChar.self, capacity: Int(entry.pointee.d_namlen)) {
String(cString: $0)
}
}
let exclude = [".", "..", ".DS_Store", ".localized"]
if exclude.contains(name) { continue }
let fullURL = url.appendingPathComponent(name)
let fullPath = fullURL.path
var st = stat()
guard lstat(fullPath, &st) == 0 else {
print("Failed to get file info for \(fullPath)")
continue
}
let isDirectory = (st.st_mode & S_IFMT) == S_IFDIR
if isDirectory {
// Recursively search subdirectories
do {
let subItems = try searchRecursive(url: fullURL)
items.append(contentsOf: subItems)
} catch {
print("Error processing directory \(fullPath): \(error)")
}
} else {
// Add non-directory files to the items array
let size = Int64(st.st_size)
let item = Item(url: fullURL, name: name, size: size)
items.append(item)
}
}
return items
}
}
-203
View File
@@ -1,203 +0,0 @@
//
// FolderDetailView.swift
// Pearcleaner
//
// Created by Alin Lupascu on 7/20/24.
//
import Foundation
import SwiftUI
//MARK: Detail View
struct FolderDetailView: View {
let rootURL: URL
@Binding var currentPath: URL?
@State private var currentItem: Item?
@State private var childItems: [Item] = []
@State private var isLoading = false
@State private var errorMessage: String?
@State private var forwardStack: [URL] = []
@State private var hoveredItem: Item?
private var fastSearch: FastFileSearch
init(rootURL: URL, currentPath: Binding<URL?>) {
self.rootURL = rootURL
self._currentPath = currentPath
self.fastSearch = FastFileSearch()
}
var body: some View {
VStack {
Spacer()
if isLoading {
ProgressView("Loading...")
} else if let errorMessage = errorMessage {
Text(errorMessage)
.foregroundColor(.red)
.padding()
} else if let currentItem = currentItem {
if !childItems.isEmpty {
TreeMapChart(items: childItems, onItemSelected: { selectedItem in
NSWorkspace.shared.selectFile(selectedItem.url.path, inFileViewerRootedAtPath: selectedItem.url.deletingLastPathComponent().path)
}, hoveredItem: $hoveredItem)
HStack {
Text("Total size: \(ByteCountFormatter.string(fromByteCount: currentItem.size, countStyle: .file))")
Spacer()
if let hoveredItem = hoveredItem {
Text("\(hoveredItem.name) (\(formatSize(hoveredItem.size)))")
}
Spacer()
VStack(alignment: .trailing) {
HStack {
RoundedRectangle(cornerRadius: 2)
.fill(LinearGradient(
gradient: Gradient(colors: [Color.cyan, Color.blue]),
startPoint: .topLeading,
endPoint: .bottomTrailing
))
.frame(width: 10, height: 10)
Text("Directory")
.padding(.trailing)
RoundedRectangle(cornerRadius: 2)
.fill(LinearGradient(
gradient: Gradient(colors: [Color.pink, Color.orange]),
startPoint: .topLeading,
endPoint: .bottomTrailing
))
.frame(width: 10, height: 10)
Text("File")
}
}
}
.padding(5)
} else {
Text("This folder is empty or there was an error loading its contents.")
.padding(5)
}
} else {
Text("No data available")
.padding(5)
}
Spacer()
}
.padding()
.onChange(of: currentPath) { newValue in
if let newPath = newValue {
loadContents(url: newPath)
}
}
// Might need this in another app where I won't use a Navigation View
// .onAppear {
// loadContents(url: rootURL)
// }
.toolbar {
// ToolbarItem(placement: .navigation) {
// Button {
// goBack()
// } label: {
// Image(systemName: "arrow.left")
// }
// .disabled(currentPath == rootURL)
// }
// ToolbarItem(placement: .navigation) {
// Button {
// goForward()
// } label: {
// Image(systemName: "arrow.right")
// }
// .disabled(forwardStack.isEmpty)
// }
ToolbarItem(placement: .principal) {
if let currentPath = currentPath {
Text(currentPath.path)
.font(.headline)
.lineLimit(1)
.truncationMode(.middle)
.padding(5)
.padding(.horizontal, 2)
.background {
RoundedRectangle(cornerRadius: 8)
.fill(.primary)
.opacity(0.05)
}
}
}
ToolbarItemGroup(placement: .automatic) {
Spacer()
Button {
printItemsCount()
} label: {
Image(systemName: "number.square")
}
Button {
refreshCurrentFolder()
} label: {
Image(systemName: "arrow.counterclockwise")
}
// Button {
// Task {
// await wipeSwiftDataStorage()
// }
// } label: {
// Image(systemName: "xmark.bin")
// }
// Button {
// Task {
// await printSwiftDataCacheSize()
// }
// } label: {
// Image(systemName: "number.square")
// }
}
}
}
private func loadContents(url: URL, wipeCache: Bool = false) {
isLoading = true
errorMessage = nil
fastSearch.search(url: url) { result in
switch result {
case .success(let items):
self.childItems = items
self.currentPath = url
self.currentItem = Item(url: url, name: url.lastPathComponent, size: items.reduce(0) { $0 + $1.size })
print("Items loaded")
case .failure(let error):
self.errorMessage = "Error loading contents: \(error.localizedDescription)"
print("Error loading contents: \(error)")
}
self.isLoading = false
}
}
private func refreshCurrentFolder() {
if let currentPath = currentPath {
loadContents(url: currentPath, wipeCache: true)
}
}
private func printItemsCount() {
print("Number of items: \(childItems.count)")
}
}
-97
View File
@@ -1,97 +0,0 @@
//
// PearDiskView.swift
// Pearcleaner
//
// Created by Alin Lupascu on 7/17/24.
//
import SwiftUI
import AlinFoundation
//MARK: Main View
struct PearDiskView: View {
@EnvironmentObject var themeManager: ThemeManager
@State private var selectedVolume: URL?
@State private var currentPath: URL?
var body: some View {
NavigationSplitView {
SidebarView(selectedVolume: $selectedVolume)
} detail: {
if let volume = selectedVolume {
FolderDetailView(rootURL: volume, currentPath: $currentPath)
} else {
Text("Select a volume")
}
}
.onChange(of: selectedVolume) { path in
currentPath = path
}
}
}
//MARK: Sidebar
struct SidebarView: View {
@EnvironmentObject var themeManager: ThemeManager
@Binding var selectedVolume: URL?
@State private var volumes: [URL] = []
var body: some View {
List(volumes, id: \.self) { volume in
Text(volume.lastPathComponent)
.onTapGesture {
selectedVolume = volume
}
}
.onAppear {
loadVolumes()
}
.background(backgroundView(themeManager: themeManager))
}
private func loadVolumes() {
let fileManager = FileManager.default
// Define the base paths for the directories you want to scan
let basePaths = [
URL(fileURLWithPath: "/Volumes"),
URL(fileURLWithPath: "/Users"),
// URL(fileURLWithPath: "/Users/alin"),
URL(fileURLWithPath: "/"),
]
for basePath in basePaths {
do {
let urls = try fileManager.contentsOfDirectory(at: basePath, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles])
// Iterate over each URL in the directory
for url in urls {
if url.isSymlink() {
// If the item is a symbolic link, resolve it
let resolvedUrl = url.resolvingSymlinksInPath()
volumes.append(resolvedUrl)
} else {
volumes.append(url)
}
}
} catch {
print("Error loading volumes: \(error)")
}
}
}
}
extension URL {
public func isSymlink() -> Bool {
do {
let _ = try self.checkResourceIsReachable()
let resourceValues = try self.resourceValues(forKeys: [.isSymbolicLinkKey])
return resourceValues.isSymbolicLink == true
} catch {
return false
}
}
}
-128
View File
@@ -1,128 +0,0 @@
////
//// SunburstChart.swift
//// Pearcleaner
////
//// Created by Alin Lupascu on 8/2/24.
////
//
//
//import SwiftUI
//import Charts
//
//final class Item: Identifiable {
// let id = UUID()
// let url: URL
// let name: String
// var size: Int64
// let isDirectory: Bool?
//
// init(url: URL, name: String, size: Int64, isDirectory: Bool = false) {
// self.url = url
// self.name = name
// self.size = size
// self.isDirectory = isDirectory
// }
//}
//
//struct SunburstChart: View {
// let chartWidth: CGFloat = 175
// let items: [Item]
//
// @State private var selectedTotal1: Double?
// @State private var selectedSegment1: Item?
//
// @State private var selectedTotal2: Double?
// @State private var selectedSegment2: Item?
//
// init(items: [Item]) {
// self.items = items
// }
//
// var body: some View {
// VStack {
// Text(selectedText)
// .font(.title)
// Text(selectedSizeText)
// .font(.title3)
//
// ZStack {
// Chart(getFiles()) { item in
// SectorMark(
// angle: .value("Size", Double(item.size)),
// innerRadius: .ratio(0.6),
// outerRadius: .ratio(0.9),
// angularInset: 1.5
// )
// .foregroundStyle(Color.blue.opacity(selectOpacity(item: item, selectedItem: selectedSegment2, ring: 2)))
// }
// .frame(width: chartWidth * 2, height: chartWidth * 2)
// .chartAngleSelection(value: $selectedTotal2)
// .onChange(of: selectedTotal2) { newValue in
// updateSelection(newValue: newValue, items: getFiles(), updateSelected: { selectedSegment2 = $0 })
// selectedSegment1 = nil
// }
//
// Chart(getDirectories()) { item in
// SectorMark(
// angle: .value("Size", Double(item.size)),
// innerRadius: .ratio(0.5),
// angularInset: 1.5
// )
// .foregroundStyle(Color.green.opacity(selectOpacity(item: item, selectedItem: selectedSegment1)))
// }
// .frame(width: chartWidth, height: chartWidth)
// .chartAngleSelection(value: $selectedTotal1)
// .onChange(of: selectedTotal1) { newValue in
// updateSelection(newValue: newValue, items: getDirectories(), updateSelected: { selectedSegment1 = $0 })
// selectedSegment2 = nil
// }
// }
// }
// }
//
// private var selectedText: String {
// if let selectedItem = selectedSegment1 ?? selectedSegment2 {
// return selectedItem.name
// } else {
// return "File System Overview"
// }
// }
//
// private var selectedSizeText: String {
// if let selectedItem = selectedSegment1 ?? selectedSegment2 {
// return ByteCountFormatter.string(fromByteCount: selectedItem.size, countStyle: .file)
// } else {
// return "Select a segment..."
// }
// }
//
// private func getDirectories() -> [Item] {
// return items.filter { $0.isDirectory == true }
// }
//
// private func getFiles() -> [Item] {
// return items.filter { $0.isDirectory == false }
// }
//
// private func selectOpacity(item: Item, selectedItem: Item?, ring: Int = 1) -> Double {
// if let selectedItem = selectedItem, selectedItem.id == item.id {
// return 1.0
// }
// return ring == 1 ? 0.8 : 0.55
// }
//
// private func updateSelection(newValue: Double?, items: [Item], updateSelected: (Item?) -> Void) {
// guard let newValue = newValue else {
// updateSelected(nil)
// return
// }
//
// var accumulatedSize: Double = 0
// let selectedItem = items.first { item in
// accumulatedSize += Double(item.size)
// return newValue <= accumulatedSize
// }
//
// updateSelected(selectedItem)
// }
//}
-260
View File
@@ -1,260 +0,0 @@
//
// TreeMap.swift
// Pearcleaner
//
// Created by Alin Lupascu on 8/1/24.
//
import Foundation
import SwiftUI
import AlinFoundation
struct TreeMapChart: View {
let items: [Item]
var onItemSelected: (Item) -> Void
@Binding var hoveredItem: Item?
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
var displayedItems: [Item] {
items//.filter { $0.size >= 1_048_576 } // Filter to keep only items with size >= 1MB
.sorted(by: { $0.size > $1.size }) // Then sort the remaining items by size
}
var body: some View {
GeometryReader { proxy in
let ld = calculateLayout(
w: proxy.size.width,
h: proxy.size.height,
data: displayedItems.map { Double($0.size) },
from: 0
)
TreeMapView(ld: ld, items: displayedItems, onItemSelected: onItemSelected, hoveredItem: $hoveredItem)
}
.ignoresSafeArea()
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
struct TreeMapView: View {
let ld: LayoutData
let items: [Item]
var onItemSelected: (Item) -> Void
@Binding var hoveredItem: Item?
@State private var hoveredIndex: Int?
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
var body: some View {
Group {
if ld.direction == .h {
HStack(spacing: 0) {
VStack(spacing: 0) {
content
}
if let child = ld.child {
TreeMapView(ld: child, items: items, onItemSelected: onItemSelected, hoveredItem: $hoveredItem)
}
}
} else {
VStack(spacing: 0) {
HStack(spacing: 0) {
content
}
if let child = ld.child {
TreeMapView(ld: child, items: items, onItemSelected: onItemSelected, hoveredItem: $hoveredItem)
}
}
}
}
}
private var content: some View {
ForEach(0..<ld.content.count, id: \.self) { i in
let file = items[ld.content[i].index]
// let maxDimension = max(ld.content[i].w, ld.content[i].h) / 5
ZStack {
Rectangle()
// .foregroundStyle(randomColor())
// .foregroundStyle(colorForItem(file))
.fill(gradientForItem(file))
.brightness(hoveredIndex == i ? 0.1 : 0)
.frame(width: ld.content[i].w, height: ld.content[i].h)
.border(.black, width: 0.5)
.onTapGesture {
onItemSelected(file)
}
.overlay {
if hoveredIndex == i {
VStack {
Text(file.name)
.minimumScaleFactor(0.5)
.lineLimit(2)
Text("\(formatSize(file.size))")
.minimumScaleFactor(0.5)
.lineLimit(1)
}
.padding(5)
.padding(.horizontal, 2)
.background(.background.opacity(0.5))
.clipShape(RoundedRectangle(cornerRadius: 8))
.zIndex(1)
}
}
}
.onHover { isHovering in
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
hoveredIndex = isHovering ? i : nil
hoveredItem = isHovering ? file : nil
}
}
}
}
// private func colorForItem(_ item: Item) -> Color {
// item.isDirectory ? .blue : .gray
// }
private func gradientForItem(_ item: Item) -> LinearGradient {
LinearGradient(
gradient: Gradient(colors: [Color.cyan, Color.blue]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
// item.isDirectory ? LinearGradient(
// gradient: Gradient(colors: [Color.cyan, Color.blue]),
// startPoint: .topLeading,
// endPoint: .bottomTrailing
// ) : LinearGradient(
// gradient: Gradient(colors: [Color.pink, Color.orange]),
// startPoint: .topLeading,
// endPoint: .bottomTrailing
// )
}
}
func formatSize(_ size: Int64) -> String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useAll]
formatter.countStyle = .file
return formatter.string(fromByteCount: size)
}
func randomColor() -> Color {
Color(hue: Double.random(in: 0.0...1.0),
saturation: Double.random(in: 0.08...0.18),
brightness: Double.random(in: 0.90...1.0))
}
final class Item: Identifiable {
let url: URL
let name: String
var size: Int64
let isDirectory: Bool?
init(url: URL, name: String, size: Int64, isDirectory: Bool = false) {
self.url = url
self.name = name
self.size = size
self.isDirectory = isDirectory
}
}
//MARK: Layout Generator ============================================================================================================
// Layout
enum Direction {
case h
case v
}
class LayoutData {
var direction: Direction = .h
var content: [(index: Int, w: Double, h: Double)] = []
var child: LayoutData? = nil
}
func calculateLayout(w: Double, h: Double, data: [Double], from: Int) -> LayoutData {
let returnData = LayoutData()
if data.isEmpty {
print("TreeMap layout data is empty")
return returnData
}
let dataToArea = w * h / data[from...].reduce(0.0, +)
if w < h {
returnData.direction = .v
} else {
returnData.direction = .h
}
let mainLength = min(w, h)
var currentIndex = from
var area = data[currentIndex] * dataToArea
var crossLength = area / mainLength
var cellRatio = mainLength / crossLength
cellRatio = max(cellRatio, 1.0 / cellRatio)
while currentIndex + 1 < data.count {
let newIndex = currentIndex + 1
let newArea = area + data[newIndex] * dataToArea
let newCrossLength = newArea / mainLength
var newCellRatio = data[newIndex] * dataToArea / newCrossLength / newCrossLength
newCellRatio = max(newCellRatio, 1.0 / newCellRatio)
if newCellRatio < cellRatio {
currentIndex = newIndex
area = newArea
crossLength = newCrossLength
cellRatio = newCellRatio
} else {
break
}
}
switch returnData.direction {
case .h:
for i in from...currentIndex {
returnData.content.append((
index: i,
w: crossLength,
h: data[i] * dataToArea / crossLength))
}
case .v:
for i in from...currentIndex {
returnData.content.append((
index: i,
w: data[i] * dataToArea / crossLength,
h: crossLength))
}
}
if currentIndex != data.count - 1 {
switch returnData.direction {
case .h:
returnData.child = calculateLayout(
w: w - crossLength,
h: h,
data: data,
from: currentIndex + 1)
case .v:
returnData.child = calculateLayout(
w: w,
h: h - crossLength,
data: data,
from: currentIndex + 1)
}
}
return returnData
}
+30 -8
View File
@@ -80,11 +80,11 @@ struct PearcleanerApp: App {
return true return true
} }
// Save window size on window dimension change // Save window size on window dimension change
.onChange(of: NSApplication.shared.windows.first?.frame) { newFrame in // .onChange(of: NSApplication.shared.windows.first?.frame) { newFrame in
if let newFrame = newFrame { // if let newFrame = newFrame {
windowSettings.saveWindowSettings(frame: newFrame) // windowSettings.saveWindowSettings(frame: newFrame)
} // }
} // }
.alert(isPresented: $appState.showUninstallAlert) { .alert(isPresented: $appState.showUninstallAlert) {
Alert( Alert(
title: Text("Warning!"), title: Text("Warning!"),
@@ -128,7 +128,7 @@ struct PearcleanerApp: App {
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
}) })
} }
#if !DEBUG #if !DEBUG
Task { Task {
@@ -141,7 +141,6 @@ struct PearcleanerApp: App {
#endif #endif
} }
} }
.windowStyle(.hiddenTitleBar) .windowStyle(.hiddenTitleBar)
.windowResizability(.contentMinSize) .windowResizability(.contentMinSize)
.commands { .commands {
@@ -171,6 +170,8 @@ struct PearcleanerApp: App {
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
var windowSettings = WindowSettings() var windowSettings = WindowSettings()
var themeManager = ThemeManager.shared var themeManager = ThemeManager.shared
var windowCloseObserver: NSObjectProtocol?
var windowFrameObserver: NSObjectProtocol?
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled") let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled")
@@ -190,16 +191,37 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
NSApplication.shared.setActivationPolicy(.accessory) NSApplication.shared.setActivationPolicy(.accessory)
} }
windowFrameObserver = NotificationCenter.default.addObserver(forName: nil, object: nil, queue: nil) { notification in
if let window = notification.object as? NSWindow, window.title == "Pearcleaner" {
if notification.name == NSWindow.didEndLiveResizeNotification || notification.name == NSWindow.didMoveNotification {
self.windowSettings.saveWindowSettings(frame: window.frame)
}
}
}
windowCloseObserver = NotificationCenter.default.addObserver(forName: NSWindow.willCloseNotification, object: nil, queue: nil) { notification in
if let window = notification.object as? NSWindow, window.title == "Pearcleaner" {
self.windowSettings.saveWindowSettings(frame: window.frame)
}
}
} }
func applicationWillTerminate(_ notification: Notification) { func applicationWillTerminate(_ notification: Notification) {
// Perform actions on app termination here // Remove the observers
if let observer = windowCloseObserver {
NotificationCenter.default.removeObserver(observer)
}
if let observer = windowFrameObserver {
NotificationCenter.default.removeObserver(observer)
}
} }
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
let windowSettings = WindowSettings() let windowSettings = WindowSettings()
+1
View File
@@ -160,6 +160,7 @@ struct Sponsor: Identifiable {
let url: URL let url: URL
static let sponsors: [Sponsor] = [ static let sponsors: [Sponsor] = [
Sponsor(name: "chris3ware", url: URL(string: "https://github.com/chris3ware")!),
Sponsor(name: "fpuhan", url: URL(string: "https://github.com/fpuhan")!), Sponsor(name: "fpuhan", url: URL(string: "https://github.com/fpuhan")!),
Sponsor(name: "HungThinhIT", url: URL(string: "https://github.com/HungThinhIT")!), Sponsor(name: "HungThinhIT", url: URL(string: "https://github.com/HungThinhIT")!),
Sponsor(name: "DharsanB", url: URL(string: "https://github.com/dharsanb")!), Sponsor(name: "DharsanB", url: URL(string: "https://github.com/dharsanb")!),
+16 -5
View File
@@ -7,7 +7,7 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
import FinderSync //import FinderSync
import AlinFoundation import AlinFoundation
import UniformTypeIdentifiers import UniformTypeIdentifiers
@@ -92,7 +92,7 @@ struct GeneralSettingsTab: View {
.foregroundStyle(.primary.opacity(0.5)) .foregroundStyle(.primary.opacity(0.5))
} }
InfoButton(text: "When deleting files using the Trash button, you can prevent accidental deletions by showing an alert before proceeding with the action") InfoButton(text: "When deleting files using the Trash button, you can prevent accidental deletions by showing an alert before proceeding with the action. Unrelated to the password prompt alert when deleting files in system folders.")
Spacer() Spacer()
Toggle(isOn: $confirmAlert, label: { Toggle(isOn: $confirmAlert, label: {
@@ -250,10 +250,21 @@ struct GeneralSettingsTab: View {
Spacer() Spacer()
Button("Extensions") { // Button("Extensions") {
FIFinderSyncController.showExtensionManagementInterface() // FIFinderSyncController.showExtensionManagementInterface()
// }
// .buttonStyle(SimpleButtonStyle(icon: "folder", help: "Show Extensions Pane"))
Toggle(isOn: $appState.finderExtensionEnabled, label: {
})
.toggleStyle(.switch)
.onChange(of: appState.finderExtensionEnabled) { newValue in
if newValue {
manageFinderPlugin(install: true)
} else {
manageFinderPlugin(install: false)
}
} }
.buttonStyle(SimpleButtonStyle(icon: "folder", help: "Show Extensions Pane"))
} }
+6 -5
View File
@@ -164,10 +164,11 @@ struct InterfaceSettingsTab: View {
.padding(.trailing) .padding(.trailing)
.foregroundStyle(.primary.opacity(0.5)) .foregroundStyle(.primary.opacity(0.5))
VStack(alignment: .leading, spacing: 5) { VStack(alignment: .leading, spacing: 5) {
Text("\(mini ? "Mini window mode" : "Full size window mode")") Text("\(mini ? "Mini window mode enabled" : "Mini window mode disabled")")
.font(.callout) .font(.callout)
.foregroundStyle(.primary.opacity(0.5)) .foregroundStyle(.primary.opacity(0.5))
} }
InfoButton(text: "Toggling between modes will reset the window frames to their default size/position")
Spacer() Spacer()
Toggle(isOn: $mini, label: { Toggle(isOn: $mini, label: {
}) })
@@ -178,7 +179,7 @@ struct InterfaceSettingsTab: View {
if mini { if mini {
appState.currentView = miniView ? .apps : .empty appState.currentView = miniView ? .apps : .empty
showPopover = false showPopover = false
windowSettings.newWindow(withView: { windowSettings.newWindow(mini: true, withView: {
MiniMode(search: $search, showPopover: $showPopover) MiniMode(search: $search, showPopover: $showPopover)
.environmentObject(appState) .environmentObject(appState)
.environmentObject(locations) .environmentObject(locations)
@@ -194,7 +195,7 @@ struct InterfaceSettingsTab: View {
} else { } else {
appState.currentView = .files appState.currentView = .files
} }
windowSettings.newWindow(withView: { windowSettings.newWindow(mini: false, withView: {
RegularMode(search: $search, showPopover: $showPopover) RegularMode(search: $search, showPopover: $showPopover)
.environmentObject(appState) .environmentObject(appState)
.environmentObject(locations) .environmentObject(locations)
@@ -313,7 +314,7 @@ struct InterfaceSettingsTab: View {
NSApplication.shared.setActivationPolicy(.regular) NSApplication.shared.setActivationPolicy(.regular)
if !hasWindowOpen() { if !hasWindowOpen() {
if mini { if mini {
windowSettings.newWindow(withView: { windowSettings.newWindow(mini: true, withView: {
MiniMode(search: $search, showPopover: $showPopover) MiniMode(search: $search, showPopover: $showPopover)
.environmentObject(appState) .environmentObject(appState)
.environmentObject(locations) .environmentObject(locations)
@@ -324,7 +325,7 @@ struct InterfaceSettingsTab: View {
.preferredColorScheme(themeManager.displayMode.colorScheme) .preferredColorScheme(themeManager.displayMode.colorScheme)
}) })
} else { } else {
windowSettings.newWindow(withView: { windowSettings.newWindow(mini: false, withView: {
RegularMode(search: $search, showPopover: $showPopover) RegularMode(search: $search, showPopover: $showPopover)
.environmentObject(appState) .environmentObject(appState)
.environmentObject(locations) .environmentObject(locations)
+78 -92
View File
@@ -23,78 +23,63 @@ struct RegularMode: View {
@Binding var showPopover: Bool @Binding var showPopover: Bool
@State private var showMenu = false @State private var showMenu = false
@State var isMenuBar: Bool = false @State var isMenuBar: Bool = false
@State private var isDrillView: Bool = false
var body: some View { var body: some View {
if isDrillView { HStack(alignment: .center, spacing: 0) {
VStack {
Toggle("Drill", isOn: $isDrillView).padding() // App List
PearDiskView() HStack(spacing: 0){
.environmentObject(appState)
if appState.reload {
VStack {
Spacer()
ProgressView() {
Text("Gathering app details")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.padding(5)
}
Spacer()
}
.frame(width: sidebarWidth)
.padding(.vertical)
} else {
AppSearchView(glass: glass, menubarEnabled: menubarEnabled, mini: mini, search: $search, showPopover: $showPopover, isMenuBar: $isMenuBar)
.frame(width: sidebarWidth)
}
}
.background(backgroundView(themeManager: themeManager, darker: true, glass: glass))
.transition(.opacity)
SlideableDivider(dimension: $sidebarWidth)
.background(backgroundView(themeManager: themeManager))
.zIndex(3)
// Details View
HStack(spacing: 0) {
Spacer()
Group {
if appState.currentView == .empty || appState.currentView == .apps {
AppDetailsEmptyView(showPopover: $showPopover)
} else if appState.currentView == .files {
FilesView(showPopover: $showPopover, search: $search)
.id(appState.appInfo.id)
} else if appState.currentView == .zombie {
ZombieView(showPopover: $showPopover, search: $search)
.id(appState.appInfo.id)
}
}
.transition(.opacity)
Spacer() Spacer()
} }
.zIndex(2)
.background(backgroundView(themeManager: themeManager)) .background(backgroundView(themeManager: themeManager))
.frame(minWidth: 900, minHeight: 600)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
} else {
HStack(alignment: .center, spacing: 0) {
// Toggle("", isOn: $isDrillView)
// App List
HStack(spacing: 0){
if appState.reload {
VStack {
Spacer()
ProgressView() {
Text("Gathering app details")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.padding(5)
}
Spacer()
}
.frame(width: sidebarWidth)
.padding(.vertical)
} else {
AppSearchView(glass: glass, menubarEnabled: menubarEnabled, mini: mini, search: $search, showPopover: $showPopover, isMenuBar: $isMenuBar)
.frame(width: sidebarWidth)
}
}
.background(backgroundView(themeManager: themeManager, darker: true, glass: glass))
.transition(.opacity)
SlideableDivider(dimension: $sidebarWidth)
.background(backgroundView(themeManager: themeManager))
.zIndex(3)
// Details View
HStack(spacing: 0) {
Spacer()
Group {
if appState.currentView == .empty || appState.currentView == .apps {
AppDetailsEmptyView(showPopover: $showPopover)
} else if appState.currentView == .files {
FilesView(showPopover: $showPopover, search: $search)
.id(appState.appInfo.id)
} else if appState.currentView == .zombie {
ZombieView(showPopover: $showPopover, search: $search)
.id(appState.appInfo.id)
}
}
.transition(.opacity)
Spacer()
}
.zIndex(2)
.background(backgroundView(themeManager: themeManager))
}
.frame(minWidth: 900, minHeight: 600)
.edgesIgnoringSafeArea(.all)
} }
.frame(minWidth: 900, minHeight: 600)
.edgesIgnoringSafeArea(.all)
} }
@@ -109,43 +94,44 @@ struct AppDetailsEmptyView: View {
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations @EnvironmentObject var locations: Locations
// @AppStorage("settings.general.animateLogo") private var animateLogo: Bool = true // @AppStorage("settings.general.animateLogo") private var animateLogo: Bool = true
@Binding var showPopover: Bool @Binding var showPopover: Bool
// @State private var animationStart = false // @State private var animationStart = false
var body: some View { var body: some View {
VStack(alignment: .center) { VStack(alignment: .center) {
Spacer() Spacer()
PearDropView() PearDropView()
// if #available(macOS 14, *) { // if #available(macOS 14, *) {
// if animateLogo && animationStart { // if animateLogo && animationStart {
// PearDropView() // PearDropView()
// .phaseAnimator([false, true]) { wwdc24, chromaRotate in // .phaseAnimator([false, true]) { wwdc24, chromaRotate in
// wwdc24 // wwdc24
// .hueRotation(.degrees(chromaRotate ? 420 : 0)) // .hueRotation(.degrees(chromaRotate ? 420 : 0))
// } animation: { chromaRotate in // } animation: { chromaRotate in
// .easeInOut(duration: 6) // .easeInOut(duration: 6)
// } // }
// } else { // } else {
// PearDropView() // PearDropView()
// } // }
// //
// } else { // } else {
// PearDropView() // PearDropView()
// } // }
Spacer() Spacer()
Text("Drop an app here") // Text("Drop an app here")
.font(.title3) // .font(.title3)
.padding(.bottom, 25) // .padding(.bottom, 25)
.opacity(0.5) // .opacity(0.5)
} }
// .onAppear { // .onAppear {
// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// animationStart = true // animationStart = true
// } // }
// } // }
} }
} }