From 8eeea2b9f863dd1eda8aee19cee67a995bfa634a Mon Sep 17 00:00:00 2001 From: Alin Date: Fri, 20 Sep 2024 15:15:53 -0600 Subject: [PATCH] v3.8.7 --- FinderOpen/Assets.xcassets/Contents.json | 6 + .../Icon.imageset/Contents.json | 21 ++ .../Icon.imageset/icon_32x32.png | Bin 0 -> 2041 bytes FinderOpen/FinderOpen.swift | 83 +----- Pearcleaner.xcodeproj/project.pbxproj | 38 +-- Pearcleaner/Logic/AppState.swift | 26 +- Pearcleaner/Logic/Utilities.swift | 14 + Pearcleaner/Logic/WindowSettings.swift | 61 ++-- Pearcleaner/PearDisk/Data.swift | 184 ------------- Pearcleaner/PearDisk/FileSearch.swift | 87 ------ Pearcleaner/PearDisk/FolderDetail.swift | 203 -------------- Pearcleaner/PearDisk/PearDiskView.swift | 97 ------- Pearcleaner/PearDisk/SunburstChart.swift | 128 --------- Pearcleaner/PearDisk/TreeMap.swift | 260 ------------------ Pearcleaner/PearcleanerApp.swift | 38 ++- Pearcleaner/Settings/About.swift | 1 + Pearcleaner/Settings/General.swift | 21 +- Pearcleaner/Settings/Interface.swift | 11 +- Pearcleaner/Views/RegularMode.swift | 170 ++++++------ 19 files changed, 235 insertions(+), 1214 deletions(-) create mode 100644 FinderOpen/Assets.xcassets/Contents.json create mode 100644 FinderOpen/Assets.xcassets/Icon.imageset/Contents.json create mode 100644 FinderOpen/Assets.xcassets/Icon.imageset/icon_32x32.png delete mode 100644 Pearcleaner/PearDisk/Data.swift delete mode 100644 Pearcleaner/PearDisk/FileSearch.swift delete mode 100644 Pearcleaner/PearDisk/FolderDetail.swift delete mode 100644 Pearcleaner/PearDisk/PearDiskView.swift delete mode 100644 Pearcleaner/PearDisk/SunburstChart.swift delete mode 100644 Pearcleaner/PearDisk/TreeMap.swift diff --git a/FinderOpen/Assets.xcassets/Contents.json b/FinderOpen/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/FinderOpen/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FinderOpen/Assets.xcassets/Icon.imageset/Contents.json b/FinderOpen/Assets.xcassets/Icon.imageset/Contents.json new file mode 100644 index 0000000..d8390ea --- /dev/null +++ b/FinderOpen/Assets.xcassets/Icon.imageset/Contents.json @@ -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 + } +} diff --git a/FinderOpen/Assets.xcassets/Icon.imageset/icon_32x32.png b/FinderOpen/Assets.xcassets/Icon.imageset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..215a2ac9437141400f78b5bc30a2eacc74d4ee5e GIT binary patch literal 2041 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$`V@X6oR9Fe^R$FWwR~i0hc6Rpa z^<~#~;v^2S6Rw4XmNtn{g(^@9m+;6NZ@?2TJRlILB?w*+^uZu;lMwKLsH`H2Xa%A| zNz*iaXzMnpaf9RBY{&6>cfI$W`wZVdyX(E=!Xqy|yJu$3oHOVD&i8-+e-7|o`9CwD z+|Pd5Fy0pjo`CA_b6rh1{}xfOwtA*;6oq+NPzHjH4=qq+wgo3{D%g` zLf9Vy@S&;-BASkk&B9ZkI{Aqo$voYnb|2)vva<3aJ*s}oFzX=gyQ`IzNa?MfZAC$= zTE-jycoCLoLS%k#NUDv+7@T)%_2`B4ZwEjjc5qmNoizVufTF=-J z^)g;jB5(s0`Twlq?@NEc#fQ$JSTyk2%$M=-^rP@RZJC0DXw ztin)Kiyr72m-_&K(7$~E`l*+}mgT@~-NyW!jhNvfb~28s%3=I`;|ea{v=FPOP-&X@ z>)*~Hp3rdW)Dtuq33VZJQJ%860o@G%5F>5M+AK)Br+%PO+ra#M3R;Ap1>=}K`BCg| z?O^Iw8qaV28?EUlVXoC-I3AXluOpkw;@Hs-hakFh$-Tb}h)_Cu279g$P=;Y(MBjuK zH2}LvI%@EY23oB;M&cem|M(Y?Y9?WqH&ClIsC<>WDB;eXJWR`i>rt2<{V))OzyN?y zxir>gp$mqxgJo0+ph`700jCC-e6}kH>@r*=aQUhRUD<}=$B|#Vjy-#)kV++J*dur| zjH)m?h7b$@2&w4zZ=FCZeFD{*jmj&(MmD8k zYVR~gM@JFUqv5m?86H^}F89!YxS`DgBQPwY{1|RSOO3IuqGpU?b&KH~%^)8XapT=h z++2DVnf!}5xHmx2-oW_yDP%^HjQwcn4w)$Er77dNkD)vkh zjh=%~f!*B9VR~i`zH4KB?G`Sae+8@8wyAs_`{zcmPg$l+HrP&bD~LdesO<#RNzwfP z?!iEJ^*$4hf_lA*(a2RKv$G-nMx_eJ&!JMQ;y1ti9hhS=dvF%jOa_gutJtU8n9h1w zyYxI7?|%sKoUle$GWx;=b!qLO7+^r&k)@6c`a5#s(%2 z%;MDP&%u$j?Ypq*7MzyLNQoj*zDy9Wb3gIvds5l{cg*%~cL4}D5dp!EEZc+DT!S9f z5j4y2Y6a-@xwG>c0c;|w$MMoXnTAUhXr&v_T}FpRg9H(E9Tcv-g+jRsk8blRz@P)9 zXMQLXN=H}uqFOEtN|WK%q7RKc>}Clzcgcqj6tMH!4OA;L7*hhIDl5SHtH{kz@pb@* zS<-6-up?y@wo4deIEp*H|D}O8{q3DhfGG5@+4SM&Z^L6swX6~>?v$!&;PE3BTwi`0 zw>HW!nWT~jB8ck()Pq%ZVb*-u=@@FYIunr{x+Tg3gb2OA42e+0(vE!*jO^n^Y!=1K zFCacM0mI%QfCg-{1)Irbax{wMktAv*YK1w#YBK56SSvp2Ee);I6kMJubr2`Dp}w`#WV>-3|cTLJg@G;t+}up0w6$h$3%Jb z2;A{0#G+cqt+FuI{^bt?2=g@!hrSXfh&s+~wSMxeSYCVzS!DxWl&+wt9D}Mi99Ew$ z@L{ytwN`bo6W@c<;gc9kr;$#lb`z1XOD68W8&)cnt=!~fNR`kfI`C`@;|J%^{L=Sv zX>kGC^6Q8=2B{6`TbYns11qth5yVl>eGrxTk27yfVlU%&WQ4n?)@H%u$*5E;FjRVF zzy)Xjb^cs#YN{1c)ubHBb~7V0-kw8;pndHO-nn=Y`FGw%t5~2Lto9g1rF+tFGC5#m z0@+N8nKOsnWEP1;JY+yX6pAJ1($ZTOdgf)D8!YOx&;5DfvBw_$hDD{@HpL@;<1)Pt zBbMW0n{mIry@Oh{PEFWML|i`mBb-fgKaXBWk$N$%>pJ4`7{#sP?8_H^zwqOyzfP0< zpk0Om$lKgA3qL#av7^VnlAV~yNc^^$=p8ggOcuczWWeEFwp^iCTqZ@%0xI*r+-2-` zp=iAM=F*>j`-^A3%kF$QsB^g&0O`^{a`cf;o;ZH|)0xq+Lj)1oJ@1D$JNUs;n<9y>a%|wdHes#na4SxgUW3QL>@mm9qN>Jm6Wd*bnG`uM7VL Xw(9u&>h4_O00000NkvXXu0mjfrYp_p literal 0 HcmV?d00001 diff --git a/FinderOpen/FinderOpen.swift b/FinderOpen/FinderOpen.swift index 6ed0901..225e71b 100644 --- a/FinderOpen/FinderOpen.swift +++ b/FinderOpen/FinderOpen.swift @@ -27,8 +27,11 @@ class FinderOpen: FIFinderSync { selectedItemURLs.count == 1, selectedItemURLs.first?.pathExtension == "app" { // Add menu item if the selected item is a .app file let menuItem = NSMenuItem(title: "Pearcleaner Uninstall", action: #selector(openInMyApp), keyEquivalent: "") -// menuItem.image = NSImage(named: NSImage.trashEmptyName) -// menu.addItem(withTitle: "Pearcleaner Uninstall", action: #selector(openInMyApp), keyEquivalent: "") + if let appIcon = NSImage(named: "Icon") { + menuItem.image = appIcon + } else { + menuItem.image = NSImage(named: NSImage.trashFullName) + } 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) -// } -// } -// -//} - diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index 202e086..8074ee5 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -11,13 +11,7 @@ C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71848432B8D2D600046CB13 /* ZombieView.swift */; }; C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.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 */; }; - 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 */; }; C7575D392B0182DE006A600A /* PearcleanerSentinel in CopyFiles */ = {isa = PBXBuildFile; fileRef = C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 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 */; }; C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */; }; 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 */; }; C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.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 = ""; }; 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 = ""; }; - C72F4B6B2C5DF4ED00B04F60 /* SunburstChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SunburstChart.swift; sourceTree = ""; }; C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathsFetch.swift; sourceTree = ""; }; - C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeMap.swift; sourceTree = ""; }; - C74D76B92C5D2DF300748DD1 /* FileSearch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileSearch.swift; sourceTree = ""; }; - C74D76BA2C5D2DF300748DD1 /* PearDiskView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PearDiskView.swift; sourceTree = ""; }; - C74D76BB2C5D2DF300748DD1 /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; - C74D76BC2C5D2DF300748DD1 /* FolderDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderDetail.swift; sourceTree = ""; }; C74FDC622BCD833D00B8960F /* Conditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conditions.swift; sourceTree = ""; }; C76D08472AF83C3F00D07867 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = ""; }; C76D08492AF83C6E00D07867 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = ""; }; @@ -133,6 +122,7 @@ C79947952C34A794007279CC /* Tips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tips.swift; sourceTree = ""; }; C7A27E7F2AFD70DD00166168 /* PearcleanerSentinel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PearcleanerSentinel.entitlements; sourceTree = ""; }; C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.alienator88.PearcleanerSentinel.plist; sourceTree = ""; }; + C7A6DBF02C9DD27200CFA042 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C7CF47232B3B3F1700979C5F /* WindowSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowSettings.swift; sourceTree = ""; }; C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = ""; }; C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = ""; }; @@ -211,19 +201,6 @@ name = Frameworks; sourceTree = ""; }; - 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 = ""; - }; C76D08462AF83C2F00D07867 /* Settings */ = { isa = PBXGroup; children = ( @@ -243,8 +220,8 @@ children = ( C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */, C77B90142AF18E7C009CC655 /* Info.plist */, - C77B90072AF18E2F009CC655 /* Assets.xcassets */, C77B90242AF2D796009CC655 /* Pearcleaner.entitlements */, + C77B90072AF18E2F009CC655 /* Assets.xcassets */, ); path = Resources; sourceTree = ""; @@ -277,7 +254,6 @@ C786F1A72C29E599001B3800 /* Builds */, C76D084F2AF84E0100D07867 /* Resources */, C76D08462AF83C2F00D07867 /* Settings */, - C74D76B82C5D2D7900748DD1 /* PearDisk */, C77B901F2AF1B390009CC655 /* Views */, C72893072AFD442600C8C1CD /* Logic */, ); @@ -305,6 +281,7 @@ C78121652BC892A000BE06BD /* FinderOpen.swift */, C78121672BC892A000BE06BD /* Info.plist */, C78121682BC892A000BE06BD /* FinderOpen.entitlements */, + C7A6DBF02C9DD27200CFA042 /* Assets.xcassets */, ); path = FinderOpen; sourceTree = ""; @@ -432,6 +409,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C7A6DBF12C9DD27200CFA042 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -451,21 +429,16 @@ buildActionMask = 2147483647; files = ( C7DD49EA2BAB4D8100CCBA16 /* AppInfoFetch.swift in Sources */, - C74D76BD2C5D2DF300748DD1 /* FileSearch.swift in Sources */, C79947962C34A794007279CC /* Tips.swift in Sources */, C76D08482AF83C3F00D07867 /* Update.swift in Sources */, C7FB173B2B96321300B96F9A /* AppsListView.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 */, C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */, C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */, C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */, C77B901A2AF1938F009CC655 /* Logic.swift in Sources */, C77B901C2AF193A3009CC655 /* Styles.swift in Sources */, - C74D76BF2C5D2DF300748DD1 /* Data.swift in Sources */, C7045A282B03E71D00376976 /* MiniMode.swift in Sources */, C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */, C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */, @@ -475,7 +448,6 @@ C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */, C76D08512AF850F300D07867 /* About.swift in Sources */, C77B90182AF19382009CC655 /* AppCommands.swift in Sources */, - C74D76BE2C5D2DF300748DD1 /* PearDiskView.swift in Sources */, C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */, C77B901E2AF1A9DB009CC655 /* SettingsWindow.swift in Sources */, C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */, diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index 9bb502e..9a2db8e 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -7,7 +7,7 @@ import Foundation import SwiftUI -import FinderSync +//import FinderSync let home = FileManager.default.homeDirectoryForCurrentUser.path @@ -79,10 +79,28 @@ class AppState: ObservableObject { } @objc func updateExtensionStatus() { - let extensionStatus = FIFinderSyncController.isExtensionEnabled - DispatchQueue.main.async { - self.finderExtensionEnabled = extensionStatus + let task = Process() + let pipe = Pipe() + + 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() { diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index 78a46a9..40e9502 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -27,12 +27,14 @@ func reloadAppsList(appState: AppState, fsm: FolderSettingsManager) { func resizeWindowAuto(windowSettings: WindowSettings, title: String) { 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) window.setContentSize(newSize) } } + // Check if Pearcleaner has any windows open func hasWindowOpen() -> Bool { 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 func caskCleanup(app: String) { diff --git a/Pearcleaner/Logic/WindowSettings.swift b/Pearcleaner/Logic/WindowSettings.swift index 17806ec..bd4487f 100644 --- a/Pearcleaner/Logic/WindowSettings.swift +++ b/Pearcleaner/Logic/WindowSettings.swift @@ -13,28 +13,20 @@ import Combine class WindowSettings { private let windowWidthKey = "windowWidthKey" private let windowHeightKey = "windowHeightKey" - private let windowWidthKeyMini = "windowWidthKeyMini" - private let windowHeightKeyMini = "windowHeightKeyMini" private let windowXKey = "windowXKey" private let windowYKey = "windowYKey" - @AppStorage("settings.general.mini") private var mini: Bool = false var windows: [NSWindow] = [] + // Register default sizes if the AppStorage keys are invalid func registerDefaultWindowSettings(completion: @escaping () -> Void = {}) { - let defaults = UserDefaults.standard - // Get primary screen let screenFrame = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 800, height: 600) - // Calculate default window sizes and x/y coordinates let defaultWidth = Float(900) // Default width for regular window - let defaultHeight = Float(600) // Default height for regular window - let defaultWidthMini = Float(300) // Default width for mini window - let defaultHeightMini = Float(370) // Default height for mini window + let defaultHeight = Float(628) // Default height for regular window 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 - // Set defaults only if they are not already set if defaults.object(forKey: windowWidthKey) == nil { defaults.set(defaultWidth, forKey: windowWidthKey) @@ -42,60 +34,69 @@ class WindowSettings { if defaults.object(forKey: windowHeightKey) == nil { 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 { defaults.set(defaultX, forKey: windowXKey) } if defaults.object(forKey: windowYKey) == nil { defaults.set(defaultY, forKey: windowYKey) } - completion() - } // Save user window settings func saveWindowSettings(frame: NSRect) { - UserDefaults.standard.set(Float(frame.size.width), forKey: mini ? windowWidthKeyMini : windowWidthKey) - UserDefaults.standard.set(Float(frame.size.height), forKey: mini ? windowHeightKeyMini : windowHeightKey) + UserDefaults.standard.set(Float(frame.size.width), forKey: windowWidthKey) + UserDefaults.standard.set(Float(frame.size.height), forKey: windowHeightKey) UserDefaults.standard.set(Float(frame.origin.x), forKey: windowXKey) UserDefaults.standard.set(Float(frame.origin.y), forKey: windowYKey) } // Load default window settings or user defined settings func loadWindowSettings() -> NSRect { - let width = CGFloat(UserDefaults.standard.float(forKey: mini ? windowWidthKeyMini : windowWidthKey)) - let height = CGFloat(UserDefaults.standard.float(forKey: mini ? windowHeightKeyMini : windowHeightKey)) + let width = CGFloat(UserDefaults.standard.float(forKey: windowWidthKey)) + let height = CGFloat(UserDefaults.standard.float(forKey: windowHeightKey)) let x = CGFloat(UserDefaults.standard.float(forKey: windowXKey)) let y = CGFloat(UserDefaults.standard.float(forKey: windowYKey)) - return NSRect(x: x, y: y, width: width, height: height) } + // 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 - func newWindow(withView view: @escaping () -> V) { + func newWindow(mini: Bool, withView view: @escaping () -> V) { + // Close existing window findAndHideWindows(named: ["Pearcleaner"]) + // Create new window using defaults let contentView = view - let frame = self.loadWindowSettings() + let frame = self.resetWindowSettings(mini: mini) let newWindow = NSWindow( contentRect: frame, styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) + newWindow.contentView = NSHostingView(rootView: contentView()) newWindow.titlebarAppearsTransparent = true newWindow.isMovableByWindowBackground = true - newWindow.center() newWindow.title = "Pearcleaner" newWindow.isRestorable = false - newWindow.titleVisibility = .hidden - newWindow.setFrameAutosaveName("Pearcleaner") - newWindow.contentView = NSHostingView(rootView: contentView()) self.windows.append(newWindow) + newWindow.titleVisibility = .hidden newWindow.makeKeyAndOrderFront(nil) - resizeWindowAuto(windowSettings: self, title: "Pearcleaner") } + } diff --git a/Pearcleaner/PearDisk/Data.swift b/Pearcleaner/PearDisk/Data.swift deleted file mode 100644 index a505f7e..0000000 --- a/Pearcleaner/PearDisk/Data.swift +++ /dev/null @@ -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() -// -// 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)") -// } -//} diff --git a/Pearcleaner/PearDisk/FileSearch.swift b/Pearcleaner/PearDisk/FileSearch.swift deleted file mode 100644 index 887356f..0000000 --- a/Pearcleaner/PearDisk/FileSearch.swift +++ /dev/null @@ -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 - } -} diff --git a/Pearcleaner/PearDisk/FolderDetail.swift b/Pearcleaner/PearDisk/FolderDetail.swift deleted file mode 100644 index baca11e..0000000 --- a/Pearcleaner/PearDisk/FolderDetail.swift +++ /dev/null @@ -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) { - 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)") - } -} diff --git a/Pearcleaner/PearDisk/PearDiskView.swift b/Pearcleaner/PearDisk/PearDiskView.swift deleted file mode 100644 index 64276b2..0000000 --- a/Pearcleaner/PearDisk/PearDiskView.swift +++ /dev/null @@ -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 - } - } -} diff --git a/Pearcleaner/PearDisk/SunburstChart.swift b/Pearcleaner/PearDisk/SunburstChart.swift deleted file mode 100644 index 730eb3b..0000000 --- a/Pearcleaner/PearDisk/SunburstChart.swift +++ /dev/null @@ -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) -// } -//} diff --git a/Pearcleaner/PearDisk/TreeMap.swift b/Pearcleaner/PearDisk/TreeMap.swift deleted file mode 100644 index eb07dc4..0000000 --- a/Pearcleaner/PearDisk/TreeMap.swift +++ /dev/null @@ -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.. 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 -} diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index 4ddc5f1..6968d72 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -80,11 +80,11 @@ struct PearcleanerApp: App { return true } // Save window size on window dimension change - .onChange(of: NSApplication.shared.windows.first?.frame) { newFrame in - if let newFrame = newFrame { - windowSettings.saveWindowSettings(frame: newFrame) - } - } +// .onChange(of: NSApplication.shared.windows.first?.frame) { newFrame in +// if let newFrame = newFrame { +// windowSettings.saveWindowSettings(frame: newFrame) +// } +// } .alert(isPresented: $appState.showUninstallAlert) { Alert( title: Text("Warning!"), @@ -128,7 +128,7 @@ struct PearcleanerApp: App { .preferredColorScheme(themeManager.displayMode.colorScheme) }) } - + #if !DEBUG Task { @@ -141,7 +141,6 @@ struct PearcleanerApp: App { #endif } } - .windowStyle(.hiddenTitleBar) .windowResizability(.contentMinSize) .commands { @@ -171,6 +170,8 @@ struct PearcleanerApp: App { class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { var windowSettings = WindowSettings() var themeManager = ThemeManager.shared + var windowCloseObserver: NSObjectProtocol? + var windowFrameObserver: NSObjectProtocol? func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { let menubarEnabled = UserDefaults.standard.bool(forKey: "settings.menubar.enabled") @@ -190,16 +191,37 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { 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) { - // 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 { let windowSettings = WindowSettings() diff --git a/Pearcleaner/Settings/About.swift b/Pearcleaner/Settings/About.swift index 5ba5769..319b012 100644 --- a/Pearcleaner/Settings/About.swift +++ b/Pearcleaner/Settings/About.swift @@ -160,6 +160,7 @@ struct Sponsor: Identifiable { let url: URL 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: "HungThinhIT", url: URL(string: "https://github.com/HungThinhIT")!), Sponsor(name: "DharsanB", url: URL(string: "https://github.com/dharsanb")!), diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index 8d5f529..53af3fd 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -7,7 +7,7 @@ import Foundation import SwiftUI -import FinderSync +//import FinderSync import AlinFoundation import UniformTypeIdentifiers @@ -92,7 +92,7 @@ struct GeneralSettingsTab: View { .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() Toggle(isOn: $confirmAlert, label: { @@ -250,10 +250,21 @@ struct GeneralSettingsTab: View { Spacer() - Button("Extensions") { - FIFinderSyncController.showExtensionManagementInterface() +// Button("Extensions") { +// 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")) } diff --git a/Pearcleaner/Settings/Interface.swift b/Pearcleaner/Settings/Interface.swift index 54aa5f7..406439b 100644 --- a/Pearcleaner/Settings/Interface.swift +++ b/Pearcleaner/Settings/Interface.swift @@ -164,10 +164,11 @@ struct InterfaceSettingsTab: View { .padding(.trailing) .foregroundStyle(.primary.opacity(0.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) .foregroundStyle(.primary.opacity(0.5)) } + InfoButton(text: "Toggling between modes will reset the window frames to their default size/position") Spacer() Toggle(isOn: $mini, label: { }) @@ -178,7 +179,7 @@ struct InterfaceSettingsTab: View { if mini { appState.currentView = miniView ? .apps : .empty showPopover = false - windowSettings.newWindow(withView: { + windowSettings.newWindow(mini: true, withView: { MiniMode(search: $search, showPopover: $showPopover) .environmentObject(appState) .environmentObject(locations) @@ -194,7 +195,7 @@ struct InterfaceSettingsTab: View { } else { appState.currentView = .files } - windowSettings.newWindow(withView: { + windowSettings.newWindow(mini: false, withView: { RegularMode(search: $search, showPopover: $showPopover) .environmentObject(appState) .environmentObject(locations) @@ -313,7 +314,7 @@ struct InterfaceSettingsTab: View { NSApplication.shared.setActivationPolicy(.regular) if !hasWindowOpen() { if mini { - windowSettings.newWindow(withView: { + windowSettings.newWindow(mini: true, withView: { MiniMode(search: $search, showPopover: $showPopover) .environmentObject(appState) .environmentObject(locations) @@ -324,7 +325,7 @@ struct InterfaceSettingsTab: View { .preferredColorScheme(themeManager.displayMode.colorScheme) }) } else { - windowSettings.newWindow(withView: { + windowSettings.newWindow(mini: false, withView: { RegularMode(search: $search, showPopover: $showPopover) .environmentObject(appState) .environmentObject(locations) diff --git a/Pearcleaner/Views/RegularMode.swift b/Pearcleaner/Views/RegularMode.swift index 6e7171b..161e5f6 100644 --- a/Pearcleaner/Views/RegularMode.swift +++ b/Pearcleaner/Views/RegularMode.swift @@ -23,78 +23,63 @@ struct RegularMode: View { @Binding var showPopover: Bool @State private var showMenu = false @State var isMenuBar: Bool = false - @State private var isDrillView: Bool = false var body: some View { - if isDrillView { - VStack { - Toggle("Drill", isOn: $isDrillView).padding() - PearDiskView() - .environmentObject(appState) + HStack(alignment: .center, spacing: 0) { + + // 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) - .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 @EnvironmentObject var appState: AppState @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 -// @State private var animationStart = false + // @State private var animationStart = false var body: some View { VStack(alignment: .center) { Spacer() + PearDropView() -// if #available(macOS 14, *) { -// if animateLogo && animationStart { -// PearDropView() -// .phaseAnimator([false, true]) { wwdc24, chromaRotate in -// wwdc24 -// .hueRotation(.degrees(chromaRotate ? 420 : 0)) -// } animation: { chromaRotate in -// .easeInOut(duration: 6) -// } -// } else { -// PearDropView() -// } -// -// } else { -// PearDropView() -// } + // if #available(macOS 14, *) { + // if animateLogo && animationStart { + // PearDropView() + // .phaseAnimator([false, true]) { wwdc24, chromaRotate in + // wwdc24 + // .hueRotation(.degrees(chromaRotate ? 420 : 0)) + // } animation: { chromaRotate in + // .easeInOut(duration: 6) + // } + // } else { + // PearDropView() + // } + // + // } else { + // PearDropView() + // } Spacer() - Text("Drop an app here") - .font(.title3) - .padding(.bottom, 25) - .opacity(0.5) + // Text("Drop an app here") + // .font(.title3) + // .padding(.bottom, 25) + // .opacity(0.5) } -// .onAppear { -// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { -// animationStart = true -// } -// } + // .onAppear { + // DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + // animationStart = true + // } + // } } }