v1.3 and v1.4

This commit is contained in:
Alin
2023-11-16 17:39:16 -07:00
parent d12dc166e9
commit 9b64ad9a5b
26 changed files with 1614 additions and 1103 deletions
+28 -18
View File
@@ -9,6 +9,9 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A272B03E71D00376976 /* MiniMode.swift */; }; C7045A282B03E71D00376976 /* MiniMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A272B03E71D00376976 /* MiniMode.swift */; };
C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A292B03FAF000376976 /* TopBarMini.swift */; }; C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A292B03FAF000376976 /* TopBarMini.swift */; };
C7045A322B068AD700376976 /* NewWin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A312B068AD700376976 /* NewWin.swift */; };
C7045A342B068B2900376976 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A332B068B2900376976 /* UpdateView.swift */; };
C7045A362B068DD700376976 /* PermView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A352B068DD700376976 /* PermView.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 */; };
C7575D382B0181FC006A600A /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7575D372B0181FC006A600A /* FileWatcher */; }; C7575D382B0181FC006A600A /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7575D372B0181FC006A600A /* FileWatcher */; };
@@ -19,7 +22,6 @@
C76D08512AF850F300D07867 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08502AF850F300D07867 /* About.swift */; }; C76D08512AF850F300D07867 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08502AF850F300D07867 /* About.swift */; };
C76D08552AF89CDE00D07867 /* AppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08542AF89CDE00D07867 /* AppListView.swift */; }; C76D08552AF89CDE00D07867 /* AppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08542AF89CDE00D07867 /* AppListView.swift */; };
C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */; }; C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */; };
C77B90062AF18E2E009CC655 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90052AF18E2E009CC655 /* ContentView.swift */; };
C77B90082AF18E2F009CC655 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C77B90072AF18E2F009CC655 /* Assets.xcassets */; }; C77B90082AF18E2F009CC655 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C77B90072AF18E2F009CC655 /* Assets.xcassets */; };
C77B90162AF19377009CC655 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90152AF19377009CC655 /* AppState.swift */; }; C77B90162AF19377009CC655 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90152AF19377009CC655 /* AppState.swift */; };
C77B90182AF19382009CC655 /* AppCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90172AF19382009CC655 /* AppCommands.swift */; }; C77B90182AF19382009CC655 /* AppCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90172AF19382009CC655 /* AppCommands.swift */; };
@@ -32,7 +34,6 @@
C7D31D482AFEB23700C7ED9E /* TopBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D472AFEB23700C7ED9E /* TopBar.swift */; }; C7D31D482AFEB23700C7ED9E /* TopBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D472AFEB23700C7ED9E /* TopBar.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 */; };
C7D31D532B0048BE00C7ED9E /* TrashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D522B0048BE00C7ED9E /* TrashView.swift */; };
C7D31D552B008CB900C7ED9E /* Shapes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D542B008CB900C7ED9E /* Shapes.swift */; }; C7D31D552B008CB900C7ED9E /* Shapes.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D542B008CB900C7ED9E /* Shapes.swift */; };
C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D582B00A49300C7ED9E /* DropTarget.swift */; }; C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D582B00A49300C7ED9E /* DropTarget.swift */; };
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; }; C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; };
@@ -63,18 +64,19 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
C7045A272B03E71D00376976 /* MiniMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniMode.swift; sourceTree = "<group>"; }; C7045A272B03E71D00376976 /* MiniMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniMode.swift; sourceTree = "<group>"; };
C7045A292B03FAF000376976 /* TopBarMini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBarMini.swift; sourceTree = "<group>"; }; C7045A292B03FAF000376976 /* TopBarMini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBarMini.swift; sourceTree = "<group>"; };
C7045A312B068AD700376976 /* NewWin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewWin.swift; sourceTree = "<group>"; };
C7045A332B068B2900376976 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
C7045A352B068DD700376976 /* PermView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermView.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
C76D084B2AF83C9D00D07867 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; }; C76D084B2AF83C9D00D07867 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; };
C76D084D2AF84D8C00D07867 /* Builds */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Builds; sourceTree = "<group>"; };
C76D08502AF850F300D07867 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; }; C76D08502AF850F300D07867 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
C76D08542AF89CDE00D07867 /* AppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListView.swift; sourceTree = "<group>"; }; C76D08542AF89CDE00D07867 /* AppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListView.swift; sourceTree = "<group>"; };
C77B90002AF18E2E009CC655 /* Pearcleaner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pearcleaner.app; sourceTree = BUILT_PRODUCTS_DIR; }; C77B90002AF18E2E009CC655 /* Pearcleaner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pearcleaner.app; sourceTree = BUILT_PRODUCTS_DIR; };
C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PearcleanerApp.swift; sourceTree = "<group>"; }; C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PearcleanerApp.swift; sourceTree = "<group>"; };
C77B90052AF18E2E009CC655 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
C77B90072AF18E2F009CC655 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; C77B90072AF18E2F009CC655 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
C77B90142AF18E7C009CC655 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; C77B90142AF18E7C009CC655 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C77B90152AF19377009CC655 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; }; C77B90152AF19377009CC655 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
@@ -90,7 +92,6 @@
C7D31D472AFEB23700C7ED9E /* TopBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBar.swift; sourceTree = "<group>"; }; C7D31D472AFEB23700C7ED9E /* TopBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBar.swift; sourceTree = "<group>"; };
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = "<group>"; }; 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>"; };
C7D31D522B0048BE00C7ED9E /* TrashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrashView.swift; sourceTree = "<group>"; };
C7D31D542B008CB900C7ED9E /* Shapes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shapes.swift; sourceTree = "<group>"; }; C7D31D542B008CB900C7ED9E /* Shapes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shapes.swift; sourceTree = "<group>"; };
C7D31D582B00A49300C7ED9E /* DropTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropTarget.swift; sourceTree = "<group>"; }; C7D31D582B00A49300C7ED9E /* DropTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropTarget.swift; sourceTree = "<group>"; };
C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; }; C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
@@ -115,6 +116,16 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
C7045A302B068ABE00376976 /* Windows */ = {
isa = PBXGroup;
children = (
C7045A312B068AD700376976 /* NewWin.swift */,
C7045A332B068B2900376976 /* UpdateView.swift */,
C7045A352B068DD700376976 /* PermView.swift */,
);
path = Windows;
sourceTree = "<group>";
};
C72893072AFD442600C8C1CD /* Logic */ = { C72893072AFD442600C8C1CD /* Logic */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -174,7 +185,6 @@
C77B8FF72AF18E2E009CC655 = { C77B8FF72AF18E2E009CC655 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C76D084D2AF84D8C00D07867 /* Builds */,
C77B90022AF18E2E009CC655 /* Pearcleaner */, C77B90022AF18E2E009CC655 /* Pearcleaner */,
C72893102AFD51EA00C8C1CD /* PearcleanerSentinel */, C72893102AFD51EA00C8C1CD /* PearcleanerSentinel */,
C77B90012AF18E2E009CC655 /* Products */, C77B90012AF18E2E009CC655 /* Products */,
@@ -198,6 +208,7 @@
C76D084F2AF84E0100D07867 /* Resources */, C76D084F2AF84E0100D07867 /* Resources */,
C76D08462AF83C2F00D07867 /* Settings */, C76D08462AF83C2F00D07867 /* Settings */,
C77B901F2AF1B390009CC655 /* Views */, C77B901F2AF1B390009CC655 /* Views */,
C7045A302B068ABE00376976 /* Windows */,
C72893072AFD442600C8C1CD /* Logic */, C72893072AFD442600C8C1CD /* Logic */,
); );
path = Pearcleaner; path = Pearcleaner;
@@ -206,13 +217,11 @@
C77B901F2AF1B390009CC655 /* Views */ = { C77B901F2AF1B390009CC655 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C77B90052AF18E2E009CC655 /* ContentView.swift */,
C77B90222AF2D616009CC655 /* FilesView.swift */, C77B90222AF2D616009CC655 /* FilesView.swift */,
C76D08542AF89CDE00D07867 /* AppListView.swift */, C76D08542AF89CDE00D07867 /* AppListView.swift */,
C7D31D472AFEB23700C7ED9E /* TopBar.swift */, C7D31D472AFEB23700C7ED9E /* TopBar.swift */,
C7045A292B03FAF000376976 /* TopBarMini.swift */, C7045A292B03FAF000376976 /* TopBarMini.swift */,
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */, C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
C7D31D522B0048BE00C7ED9E /* TrashView.swift */,
C7D31D582B00A49300C7ED9E /* DropTarget.swift */, C7D31D582B00A49300C7ED9E /* DropTarget.swift */,
C7045A272B03E71D00376976 /* MiniMode.swift */, C7045A272B03E71D00376976 /* MiniMode.swift */,
); );
@@ -335,13 +344,13 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C76D08482AF83C3F00D07867 /* Update.swift in Sources */, C76D08482AF83C3F00D07867 /* Update.swift in Sources */,
C7045A342B068B2900376976 /* UpdateView.swift in Sources */,
C77B90162AF19377009CC655 /* AppState.swift in Sources */, C77B90162AF19377009CC655 /* AppState.swift in Sources */,
C7045A322B068AD700376976 /* NewWin.swift in Sources */,
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */, C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */,
C77B901A2AF1938F009CC655 /* Logic.swift in Sources */, C77B901A2AF1938F009CC655 /* Logic.swift in Sources */,
C7D31D532B0048BE00C7ED9E /* TrashView.swift in Sources */,
C7D31D462AFDA90C00C7ED9E /* Sentinel.swift in Sources */, C7D31D462AFDA90C00C7ED9E /* Sentinel.swift in Sources */,
C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */, C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */,
C77B90062AF18E2E009CC655 /* ContentView.swift in Sources */,
C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */, C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */,
C77B901C2AF193A3009CC655 /* Styles.swift in Sources */, C77B901C2AF193A3009CC655 /* Styles.swift in Sources */,
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */, C7045A282B03E71D00376976 /* MiniMode.swift in Sources */,
@@ -350,6 +359,7 @@
C76D084A2AF83C6E00D07867 /* General.swift in Sources */, C76D084A2AF83C6E00D07867 /* General.swift in Sources */,
C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */, C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */,
C7D31D552B008CB900C7ED9E /* Shapes.swift in Sources */, C7D31D552B008CB900C7ED9E /* Shapes.swift in Sources */,
C7045A362B068DD700376976 /* PermView.swift in Sources */,
C76D08512AF850F300D07867 /* About.swift in Sources */, C76D08512AF850F300D07867 /* About.swift in Sources */,
C77B90182AF19382009CC655 /* AppCommands.swift in Sources */, C77B90182AF19382009CC655 /* AppCommands.swift in Sources */,
C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */, C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */,
@@ -525,7 +535,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3; CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU; DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -534,15 +544,15 @@
INFOPLIST_FILE = Pearcleaner/Resources/Info.plist; INFOPLIST_FILE = Pearcleaner/Resources/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Pearcleaner; INFOPLIST_KEY_CFBundleDisplayName = Pearcleaner;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSAppleEventsUsageDescription = "App needs to send apple events to FInder to delete files"; INFOPLIST_KEY_NSAppleEventsUsageDescription = "Pearcleaner needs to send apple events to FInder to delete files";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSSystemAdministrationUsageDescription = "Requires full disk access to be able to delete files in locations with no permission"; INFOPLIST_KEY_NSSystemAdministrationUsageDescription = "Pearcleaner requires full disk access to be able to delete files in locations with no permission";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.2; MARKETING_VERSION = 1.4;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -559,7 +569,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3; CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU; DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -568,15 +578,15 @@
INFOPLIST_FILE = Pearcleaner/Resources/Info.plist; INFOPLIST_FILE = Pearcleaner/Resources/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Pearcleaner; INFOPLIST_KEY_CFBundleDisplayName = Pearcleaner;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSAppleEventsUsageDescription = "App needs to send apple events to FInder to delete files"; INFOPLIST_KEY_NSAppleEventsUsageDescription = "Pearcleaner needs to send apple events to FInder to delete files";
INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSSystemAdministrationUsageDescription = "Requires full disk access to be able to delete files in locations with no permission"; INFOPLIST_KEY_NSSystemAdministrationUsageDescription = "Pearcleaner requires full disk access to be able to delete files in locations with no permission";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.2; MARKETING_VERSION = 1.4;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
+1 -1
View File
@@ -23,7 +23,7 @@ struct AppCommands: Commands {
Button { Button {
loadGithubReleases(appState: appState, manual: true) loadGithubReleases(appState: appState, manual: true)
} label: { } label: {
Text("Updates") Text("Check for Updates")
} }
.keyboardShortcut("u", modifiers: .command) .keyboardShortcut("u", modifiers: .command)
+14 -7
View File
@@ -22,8 +22,11 @@ class AppState: ObservableObject
// @Published var currentMode = CurrentMode.regular // @Published var currentMode = CurrentMode.regular
@Published var showAlert: Bool = false @Published var showAlert: Bool = false
@Published var sidebar: Bool = true @Published var sidebar: Bool = true
@Published var showPopover: Bool = false
@Published var isReminderVisible: Bool = false @Published var isReminderVisible: Bool = false
@Published var releases = [Release]() @Published var releases = [Release]()
@Published var progressBar: (String, Double) = ("Ready", 0.0)
//Window //Window
// @Published var winWidth: CGFloat = 1020 // @Published var winWidth: CGFloat = 1020
@@ -35,7 +38,8 @@ class AppState: ObservableObject
bundleIdentifier: "", bundleIdentifier: "",
appName: "", appName: "",
appVersion: "", appVersion: "",
appIcon: nil appIcon: nil,
webApp: false
) )
} }
} }
@@ -47,8 +51,9 @@ struct AppInfo: Identifiable, Hashable {
let appName: String let appName: String
let appVersion: String let appVersion: String
let appIcon: NSImage? let appIcon: NSImage?
let webApp: Bool
static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil) static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil, webApp: false)
} }
@@ -78,11 +83,13 @@ enum CurrentDetailsView:Int
case apps case apps
} }
//enum CurrentMode:Int enum NewWindow:Int
//{ {
// case regular case update
// case mini case no_update
//} case perm
}
enum AlertType:Int enum AlertType:Int
{ {
+51 -19
View File
@@ -77,6 +77,7 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String { var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String {
var appIcon: NSImage? var appIcon: NSImage?
var appName: String? var appName: String?
var webApp: Bool?
if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String { if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String {
appName = localizedName appName = localizedName
} else if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { } else if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String {
@@ -111,7 +112,13 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
print("App Icon not found for app at path: \(path)") print("App Icon not found for app at path: \(path)")
} }
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName ?? "", appVersion: appVersion, appIcon: appIcon) if bundle.infoDictionary?["LSTemplateApplication"] is Bool {
webApp = true
} else {
webApp = false
}
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName ?? "", appVersion: appVersion, appIcon: appIcon, webApp: webApp ?? false)
} else { } else {
print("One or more variables missing for app at path: \(path)") print("One or more variables missing for app at path: \(path)")
@@ -178,6 +185,9 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) {
// appState.paths.insert(url, at: 0) // appState.paths.insert(url, at: 0)
collection.insert(url, at: 0) collection.insert(url, at: 0)
} }
// Only search for extra files if not a webapp
// if !appInfo.webApp {
let fileManager = FileManager.default let fileManager = FileManager.default
let dispatchGroup = DispatchGroup() let dispatchGroup = DispatchGroup()
let bundleComponents = appInfo.bundleIdentifier.components(separatedBy: ".") let bundleComponents = appInfo.bundleIdentifier.components(separatedBy: ".")
@@ -203,29 +213,43 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) {
if collection.contains(itemURL) { if collection.contains(itemURL) {
return return
} }
// Catch web app plist files
if itemL.contains("comapple") { if appInfo.webApp {
if itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 4 && itemL.contains(nameL)) { if itemL.contains(bundleIdentifierL) {
collection.append(itemURL) if collection.contains(itemURL) {
} return
} }
else if itemL.contains("xcode") {
if itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 4 && itemL.contains(nameL)) {
collection.append(itemURL)
}
} else if itemL.contains("logi") && !itemL.contains("login") {
if itemL.contains(bundleComponents[1]) || itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 3 && itemL.contains(nameL)) {
collection.append(itemURL)
}
} else if itemL.contains("office") || itemL.contains("oneauth") || itemL.suffix(2).contains("ms") || itemL.contains("onenote") {
if itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 4 && itemL.contains(nameL)) {
collection.append(itemURL) collection.append(itemURL)
} }
} else { } else {
if itemL.contains(bundleIdentifierL) || itemL.contains(bundle) || (nameL.count > 3 && itemL.contains(nameL)) { // Catch all the com.apple folders in the OS since there's a ton and probably unrelated
collection.append(itemURL) if itemL.contains("comapple") {
if itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 4 && itemL.contains(nameL)) {
collection.append(itemURL)
}
}
// Catch Xcode files
else if itemL.contains("xcode") {
if itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 4 && itemL.contains(nameL)) {
collection.append(itemURL)
}
// Catch Logitech files since Logi is part of word login and can return random files
} else if itemL.contains("logi") && !itemL.contains("login") {
if itemL.contains(bundleComponents[1]) || itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 3 && itemL.contains(nameL)) {
collection.append(itemURL)
}
// Catch MS Office files since they have many random folder names and very short names
} else if itemL.contains("office") || itemL.contains("oneauth") || itemL.suffix(2).contains("ms") || itemL.contains("onenote") {
if itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 4 && itemL.contains(nameL)) {
collection.append(itemURL)
}
} else {
if itemL.contains(bundleIdentifierL) || itemL.contains(bundle) || (nameL.count > 3 && itemL.contains(nameL)) {
collection.append(itemURL)
}
} }
} }
} }
} catch { } catch {
continue continue
@@ -241,6 +265,14 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) {
} }
} }
// }
// if appInfo.webApp {
// updateOnMain {
// appState.paths = collection
// appState.selectedItems = Set(collection)
// }
// }
} }
} }
File diff suppressed because it is too large Load Diff
+37 -15
View File
@@ -143,7 +143,10 @@ struct SimpleSearchStyle: TextFieldStyle {
@State private var isHovered = false @State private var isHovered = false
@State var icon: Image? @State var icon: Image?
@State var trash: Bool = false @State var trash: Bool = false
@Binding var reload: Bool
@Binding var text: String @Binding var text: String
@EnvironmentObject var appState: AppState
@AppStorage("settings.general.mini") private var mini: Bool = false
func _body(configuration: TextField<Self._Label>) -> some View { func _body(configuration: TextField<Self._Label>) -> some View {
@@ -156,21 +159,40 @@ struct SimpleSearchStyle: TextFieldStyle {
} }
configuration configuration
.frame(maxWidth: 300) // .frame(minWidth: mini ? 80 : 300)
// .frame(maxWidth: mini ? 150 : 300)
.font(.title3) .font(.title3)
.textFieldStyle(PlainTextFieldStyle()) .textFieldStyle(PlainTextFieldStyle())
if trash {
Image(systemName: "xmark") Image(systemName: "arrow.triangle.2.circlepath")
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 10, height: 10) .frame(width: 15, height: 15)
.padding(.trailing, 5) .padding(.trailing, 5)
.foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5)) .foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5))
.onTapGesture { .onTapGesture {
text = "" // Refresh Apps list
reload.toggle()
let sortedApps = getSortedApps()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.sortedApps.userApps = sortedApps.userApps
appState.sortedApps.systemApps = sortedApps.systemApps
reload.toggle()
} }
.opacity(text.isEmpty ? 0 : 1) }
}
// if trash {
// Image(systemName: "xmark")
// .resizable()
// .aspectRatio(contentMode: .fit)
// .frame(width: 10, height: 10)
// .padding(.trailing, 5)
// .foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5))
// .onTapGesture {
// text = ""
// }
// .opacity(text.isEmpty ? 0 : 1)
// }
} }
} }
@@ -242,9 +264,9 @@ struct WindowActionButton: ButtonStyle {
.background( .background(
!configuration.isPressed ? !configuration.isPressed ?
!hovered ? !hovered ?
action == .accept ? Color("AccentColor") : Color.red : action == .accept ? Color("button") : Color.red :
action == .accept ? Color("AccentColor").opacity(0.8) : Color.red.opacity(0.8) : action == .accept ? Color("button").opacity(0.8) : Color.red.opacity(0.8) :
action == .accept ? Color("AccentColor").opacity(0.5) : Color.red.opacity(0.5) action == .accept ? Color("button").opacity(0.5) : Color.red.opacity(0.5)
) )
.foregroundColor(.white) .foregroundColor(.white)
.cornerRadius(8) .cornerRadius(8)
+122 -23
View File
@@ -25,35 +25,98 @@ func updateOnBackground(_ updates: @escaping () -> Void) {
// Resize window // Resize window
func resizeWindow() { func resizeWindow(width: CGFloat, height: CGFloat) {
if let window = NSApplication.shared.windows.first { if let window = NSApplication.shared.windows.first {
let newSize = NSSize(width: 500, height: 450) let newSize = NSSize(width: width, height: height)
window.setContentSize(newSize) window.setContentSize(newSize)
} }
} }
// Check FDA
func checkFullDiskAccessForApp() -> Bool {
let process = Process()
process.launchPath = "/usr/bin/sqlite3"
process.arguments = ["/Library/Application Support/com.apple.TCC/TCC.db", "select client from access where auth_value and service = \"kTCCServiceSystemPolicyAllFiles\" and client = \"com.alienator88.Pearcleaner\""]
let pipe = Pipe()
let pipeErr = Pipe()
process.standardOutput = pipe
process.standardError = pipeErr
process.launch()
// let data = pipe.fileHandleForReading.readDataToEndOfFile()
let dataErr = pipeErr.fileHandleForReading.readDataToEndOfFile()
let output = String(data: dataErr, encoding: .utf8)
// Check if the app is in the results
if let result = output, result.isEmpty {
return true
} else {
return false
}
}
// Check for access to Full Disk access // Check for access to Full Disk access
func checkAndRequestFullDiskAccess(appState: AppState, skipAlert: Bool = false) -> Bool { func checkAndRequestFullDiskAccess(appState: AppState, skipAlert: Bool = false) -> Bool {
@AppStorage("settings.permissions.disk") var diskP: Bool = false @AppStorage("settings.permissions.disk") var diskP: Bool = false
let process = Process()
process.launchPath = "/usr/bin/sqlite3"
process.arguments = ["/Library/Application Support/com.apple.TCC/TCC.db", "select client from access where auth_value and service = \"kTCCServiceSystemPolicyAllFiles\" and client = \"com.alienator88.Pearcleaner\""]
let fileURL = URL(fileURLWithPath: "\(home)/Library/Application Support/com.apple.TCC/TCC.db") let pipe = Pipe()
let pipeErr = Pipe()
process.standardOutput = pipe
process.standardError = pipeErr
process.launch()
let accessStatus = FileManager.default.isWritableFile(atPath: fileURL.path) // let data = pipe.fileHandleForReading.readDataToEndOfFile()
if accessStatus { let dataErr = pipeErr.fileHandleForReading.readDataToEndOfFile()
let output = String(data: dataErr, encoding: .utf8)
// Check if the app is in the results
if let result = output, result.isEmpty {
diskP = true diskP = true
_ = checkAndRequestAccessibilityAccess(appState: appState) _ = checkAndRequestAccessibilityAccess(appState: appState)
return true return true
} else { } else {
diskP = false diskP = false
if !skipAlert { if !skipAlert {
appState.alertType = .diskAccess NewWin.show(appState: appState, width: 500, height: 350, newWin: .perm)
appState.showAlert = true // appState.alertType = .diskAccess
// appState.showAlert = true
} }
return false return false
} }
// let fileURL = URL(fileURLWithPath: "/Library/Application Support/com.apple.TCC/TCC.db")
//
// let accessStatus = FileManager.default.isReadableFile(atPath: fileURL.path)
// print(accessStatus)
// if accessStatus {
// diskP = true
// _ = checkAndRequestAccessibilityAccess(appState: appState)
//
// return true
// } else {
// diskP = false
// if !skipAlert {
// appState.alertType = .diskAccess
// appState.showAlert = true
// }
//
// return false
// }
} }
@@ -74,6 +137,22 @@ func checkAndRequestAccessibilityAccess(appState: AppState) -> Bool {
} }
// Check if appearance is dark mode
func isDarkModeEnabled() -> Bool {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/bin/zsh")
process.arguments = ["-c", "defaults read -g AppleInterfaceStyle"]
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
try? process.run()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
if !data.isEmpty{
return true
} else {
return false
}
}
// Check if symlink // Check if symlink
@@ -277,35 +356,28 @@ func presentAlert(appState: AppState) -> Alert {
switch appState.alertType { switch appState.alertType {
case .update: case .update:
return Alert(title: Text("Update Available 🥳"), message: Text("You may choose to install the update now, otherwise you may check again later from Settings"), primaryButton: .default(Text("Open")) { return Alert(title: Text("Update Available 🥳"), message: Text("You may choose to install the update now, otherwise you may check again later from Settings"), primaryButton: .default(Text("Install")) {
// downloadUpdate(appState: appState) downloadUpdate(appState: appState)
launchUpdate() // launchUpdate()
appState.alertType = .off appState.alertType = .off
}, secondaryButton: .cancel()) }, secondaryButton: .cancel())
case .no_update: case .no_update:
return Alert(title: Text("No Updates 😌"), message: Text("The application is on the latest release available"), primaryButton: .cancel(Text("Okay")), secondaryButton: .default(Text("Force Update")) { return Alert(title: Text("No Updates 😌"), message: Text("Pearcleaner is on the latest release available"), primaryButton: .cancel(Text("Okay")), secondaryButton: .default(Text("Force Update")) {
downloadUpdate(appState: appState) downloadUpdate(appState: appState)
appState.alertType = .off appState.alertType = .off
}) })
case .diskAccess: case .diskAccess:
return Alert(title: Text("Permissions"), message: Text("This application requires Full Disk and Accessibility permissions. Drag the app into the Full Disk pane if not visible."), primaryButton: .default(Text("Allow in Settings")) { return Alert(title: Text("Permissions"), message: Text("Pearcleaner requires Full Disk and Accessibility permissions. Drag the app into the Full Disk and Accessibility pane to enable or toggle On if already present."), primaryButton: .default(Text("Allow in Settings")) {
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") { if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") {
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
} }
appState.alertType = .off appState.alertType = .off
}, secondaryButton: .cancel(Text("Later"))) }, secondaryButton: .cancel(Text("Later")))
// case .eventsAccess:
// return Alert(title: Text("Accessibility Permission"), message: Text("This application requires Accessibility permission"), primaryButton: .default(Text("Allow in Settings")) {
// if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") {
// NSWorkspace.shared.open(url)
// }
// appState.alertType = .off
// }, secondaryButton: .cancel(Text("Later")))
case .restartApp: case .restartApp:
return Alert(title: Text("Update Completed!"), message: Text("The application has been updated to the latest version, would you like to restart now?"), primaryButton: .default(Text("Restart")), secondaryButton: .cancel(Text("Later")) { return Alert(title: Text("Update Completed!"), message: Text("The application has been updated to the latest version, would you like to restart now?"), primaryButton: .default(Text("Restart")) {
appState.alertType = .off appState.alertType = .off
relaunchApp(afterDelay: 1) relaunchApp()
}) }, secondaryButton: .cancel(Text("Later")))
case .off: case .off:
return Alert(title: Text("")) return Alert(title: Text(""))
} }
@@ -326,7 +398,34 @@ func ensureApplicationSupportFolderExists(appState: AppState) {
} }
// --- Write Log to File for troubleshooting ---
func writeLog(string: String) {
let fileManager = FileManager.default
let home = fileManager.homeDirectoryForCurrentUser.path
let logFilePath = "\(home)/Downloads/log.txt"
// Check if the log file exists, and create it if it doesn't
if !fileManager.fileExists(atPath: logFilePath) {
if !fileManager.createFile(atPath: logFilePath, contents: nil, attributes: nil) {
print("Failed to create the log file.")
return
}
}
do {
if let fileHandle = FileHandle(forWritingAtPath: logFilePath) {
let ns = "\(string)\n"
fileHandle.seekToEndOfFile()
fileHandle.write(ns.data(using: .utf8)!)
fileHandle.closeFile()
} else {
print("Error opening file for appending")
}
}
}
// --- Load Plist file with launchctl ---
func launchctl(load: Bool) { func launchctl(load: Bool) {
let cmd = load ? "load" : "unload" let cmd = load ? "load" : "unload"
if let plistPath = Bundle.main.path(forResource: "com.alienator88.PearcleanerSentinel", ofType: "plist") { if let plistPath = Bundle.main.path(forResource: "com.alienator88.PearcleanerSentinel", ofType: "plist") {
+55 -96
View File
@@ -15,108 +15,66 @@ struct PearcleanerApp: App {
@AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1 @AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1
@AppStorage("settings.permissions.disk") private var diskP: Bool = false @AppStorage("settings.permissions.disk") private var diskP: Bool = false
@AppStorage("settings.permissions.events") private var diskE: Bool = false @AppStorage("settings.permissions.events") private var diskE: Bool = false
@AppStorage("displayMode") var displayMode: DisplayMode = .dark @AppStorage("displayMode") var displayMode: DisplayMode = .system
@AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false
@State private var search = "" @State private var search = ""
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
if !mini { Group {
AppListView(search: $search)
.environmentObject(appState) if !mini {
.frame(minWidth: 1020, minHeight: 700) AppListView(search: $search)
.preferredColorScheme(displayMode.colorScheme) .frame(minWidth: 800, minHeight: 500)
.alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) } } else {
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*")) MiniMode(search: $search)
.onAppear { .frame(minWidth: 300, minHeight: 300)
NSWindow.allowsAutomaticWindowTabbing = false }
// Get Apps
let sortedApps = getSortedApps()
appState.sortedApps.userApps = sortedApps.userApps
appState.sortedApps.systemApps = sortedApps.systemApps
Task {
#if !DEBUG
// Make sure App Support folder exists in the future if needed for storage
ensureApplicationSupportFolderExists(appState: appState)
let fileManager = FileManager.default
let destinationURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("Pearcleaner").appendingPathComponent("Pearcleaner.zip")
try? fileManager.removeItem(at: destinationURL)
// Check for updates
if diskP && diskE {
loadGithubReleases(appState: appState)
}
// Check for disk/accessibility permissions
_ = checkAndRequestFullDiskAccess(appState: appState)
// TIMERS ////////////////////////////////////////////////////////////////////////////////////
// Check for app updates every 8 hours or whatever user saved setting. Also refresh autosuggestion list
let updateSeconds = updateTimeframe.daysToSeconds
_ = Timer.scheduledTimer(withTimeInterval: updateSeconds, repeats: true) { _ in
DispatchQueue.main.async {
loadGithubReleases(appState: appState)
}
}
#endif
}
}
} else {
MiniMode(search: $search)
.environmentObject(appState)
.frame(minWidth: 500, minHeight: 450)
.preferredColorScheme(displayMode.colorScheme)
.alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) }
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
.onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
// Get Apps
let sortedApps = getSortedApps()
appState.sortedApps.userApps = sortedApps.userApps
appState.sortedApps.systemApps = sortedApps.systemApps
Task {
#if !DEBUG
// Make sure App Support folder exists in the future if needed for storage
ensureApplicationSupportFolderExists(appState: appState)
let fileManager = FileManager.default
let destinationURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("Pearcleaner").appendingPathComponent("Pearcleaner.zip")
try? fileManager.removeItem(at: destinationURL)
// Check for updates
if diskP && diskE {
loadGithubReleases(appState: appState)
}
// Check for disk/accessibility permissions
_ = checkAndRequestFullDiskAccess(appState: appState)
// TIMERS ////////////////////////////////////////////////////////////////////////////////////
// Check for app updates every 8 hours or whatever user saved setting. Also refresh autosuggestion list
let updateSeconds = updateTimeframe.daysToSeconds
_ = Timer.scheduledTimer(withTimeInterval: updateSeconds, repeats: true) { _ in
DispatchQueue.main.async {
loadGithubReleases(appState: appState)
}
}
#endif
}
}
} }
.environmentObject(appState)
.preferredColorScheme(displayMode.colorScheme)
.alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) }
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
.onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
// Get Apps
let sortedApps = getSortedApps()
appState.sortedApps.userApps = sortedApps.userApps
appState.sortedApps.systemApps = sortedApps.systemApps
Task {
#if !DEBUG
// Make sure App Support folder exists in the future if needed for storage
ensureApplicationSupportFolderExists(appState: appState)
// Check for updates
if diskP && diskE {
loadGithubReleases(appState: appState)
}
// Check for disk/accessibility permissions
_ = checkAndRequestFullDiskAccess(appState: appState)
// TIMERS ////////////////////////////////////////////////////////////////////////////////////
// Check for app updates every 8 hours or whatever user saved setting. Also refresh autosuggestion list
let updateSeconds = updateTimeframe.daysToSeconds
_ = Timer.scheduledTimer(withTimeInterval: updateSeconds, repeats: true) { _ in
DispatchQueue.main.async {
loadGithubReleases(appState: appState)
}
}
#endif
}
}
} }
.windowStyle(.hiddenTitleBar) .windowStyle(.hiddenTitleBar)
@@ -125,6 +83,7 @@ struct PearcleanerApp: App {
.commands { .commands {
AppCommands(appState: appState) AppCommands(appState: appState)
CommandGroup(replacing: .newItem, addition: { }) CommandGroup(replacing: .newItem, addition: { })
} }
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0x83",
"red" : "0x24"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0x83",
"red" : "0x24"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xFF",
"red" : "0xFF"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0x26",
"green" : "0x26",
"red" : "0x26"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
+12 -2
View File
@@ -7,16 +7,18 @@
<dict> <dict>
<key>CFBundleTypeName</key> <key>CFBundleTypeName</key>
<string>Application Bundle</string> <string>Application Bundle</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>CFBundleTypeRole</key> <key>CFBundleTypeRole</key>
<string>Viewer</string> <string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key> <key>LSItemContentTypes</key>
<array> <array>
<string>com.apple.application-bundle</string> <string>com.apple.application-bundle</string>
</array> </array>
</dict> </dict>
</array> </array>
<key>CFBundleGetInfoString</key>
<string></string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
<array> <array>
<dict> <dict>
@@ -30,5 +32,13 @@
</array> </array>
</dict> </dict>
</array> </array>
<key>SUPublicEDKey</key>
<string>cD156JWtfwtgFJH2RApyj9Bgc0fCqLBGEf9C9IRMCWE=</string>
<key>SUFeedURL</key>
<string>https://raw.githubusercontent.com/alienator88/Pearcleaner/main/appcast.xml</string>
<key>SUEnableInstallerLauncherService</key>
<true/>
<key>SUEnableDownloaderService</key>
<true/>
</dict> </dict>
</plist> </plist>
+1 -1
View File
@@ -139,7 +139,7 @@ struct AboutSettingsTab: View {
Spacer() Spacer()
Text("Made with ❤️ by Alin Lupascu").font(.footnote).padding(.bottom) Text("Made with ❤️ by Alin Lupascu (dev@itsalin.com)").font(.footnote).padding(.bottom)
} }
.padding(20) .padding(20)
.frame(width: 750, height: 325) .frame(width: 750, height: 325)
+47 -10
View File
@@ -13,8 +13,10 @@ struct GeneralSettingsTab: View {
@AppStorage("settings.general.glass") private var glass: Bool = true @AppStorage("settings.general.glass") private var glass: Bool = true
@AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false
@AppStorage("settings.general.dark") var isDark: Bool = true @AppStorage("settings.general.dark") var isDark: Bool = true
@AppStorage("displayMode") var displayMode: DisplayMode = .dark @AppStorage("displayMode") var displayMode: DisplayMode = .system
@State private var selectedTheme = "Auto"
private let themes = ["Auto", "Dark", "Light"]
var body: some View { var body: some View {
Form { Form {
VStack { VStack {
@@ -38,18 +40,44 @@ struct GeneralSettingsTab: View {
HStack { HStack {
VStack(alignment: .leading, spacing: 5) { VStack(alignment: .leading, spacing: 5) {
Text("Dark Mode").font(.title2) Text("Appearance").font(.title2)
Text("Toggles between dark and light mode") Text("Toggles color mode")
.font(.footnote) .font(.footnote)
.foregroundStyle(.gray) .foregroundStyle(.gray)
} }
Spacer() Spacer()
Toggle(isOn: $isDark, label: {
}) Picker("", selection: $selectedTheme) {
.toggleStyle(.switch) ForEach(themes, id: \.self) { theme in
.onChange(of: isDark) { newValue in Text(theme)
displayMode.colorScheme = newValue ? .dark : .light }
} }
.pickerStyle(SegmentedPickerStyle())
// .padding()
.onChange(of: selectedTheme) { newTheme in
switch newTheme {
case "Auto":
displayMode.colorScheme = nil
if isDarkModeEnabled() {
displayMode.colorScheme = .dark
} else {
displayMode.colorScheme = .light
}
case "Dark":
displayMode.colorScheme = .dark
case "Light":
displayMode.colorScheme = .light
default:
break
}
}
// Toggle(isOn: $isDark, label: {
// })
// .toggleStyle(.switch)
// .onChange(of: isDark) { newValue in
// displayMode.colorScheme = newValue ? .dark : .light
// }
} }
.padding() .padding()
.background( .background(
@@ -69,7 +97,16 @@ struct GeneralSettingsTab: View {
}) })
.toggleStyle(.switch) .toggleStyle(.switch)
.onChange(of: mini) { newVal in .onChange(of: mini) { newVal in
resizeWindow() resizeWindow(width: 300, height: 300)
if mini {
appState.currentView = .apps
} else {
if appState.appInfo.appName.isEmpty {
appState.currentView = .empty
} else {
appState.currentView = .files
}
}
} }
} }
.padding() .padding()
+5 -1
View File
@@ -39,13 +39,17 @@ struct SentinelSettingsTab: View {
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 8)
.fill(Color("mode").opacity(0.05)) .fill(Color("mode").opacity(0.05))
) )
Text("If you uninstall Pearcleaner, make sure to disable Sentinel first").font(.footnote)
.foregroundStyle(Color.red)
.padding()
} }
} }
.padding(20) .padding(20)
.frame(width: 450, height: 100) .frame(width: 450, height: 130)
} }
+66 -18
View File
@@ -93,7 +93,8 @@ struct UpdateSettingsTab: View {
func loadGithubReleases(appState: AppState, manual: Bool = false) { func loadGithubReleases(appState: AppState, manual: Bool = false) {
let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/releases")! let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/releases")!
let request = URLRequest(url: url) let request = URLRequest(url: url)
// request.setValue("token \(ghToken)", forHTTPHeaderField: "Authorization") // Set the token for a private repo
// request.setValue("token \(ghToken)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data { if let data = data {
if let decodedResponse = try? JSONDecoder().decode([Release].self, from: data) { if let decodedResponse = try? JSONDecoder().decode([Release].self, from: data) {
@@ -114,22 +115,25 @@ func checkForUpdate(appState: AppState, manual: Bool = false) {
guard let latestRelease = appState.releases.first else { return } guard let latestRelease = appState.releases.first else { return }
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
if latestRelease.tag_name > currentVersion ?? "" { if latestRelease.tag_name > currentVersion ?? "" {
appState.alertType = .update NewWin.show(appState: appState, width: 500, height: 400, newWin: .update)
appState.showAlert = true // appState.alertType = .update
// appState.showAlert = true
} else { } else {
if manual { if manual {
appState.alertType = .no_update NewWin.show(appState: appState, width: 500, height: 300, newWin: .no_update)
appState.showAlert = true // appState.alertType = .no_update
// appState.showAlert = true
} }
} }
} }
func launchUpdate() {
NSWorkspace.shared.open(URL(string: "https://github.com/alienator88/Pearcleaner/releases")!)
}
func downloadUpdate(appState: AppState) { func downloadUpdate(appState: AppState) {
updateOnMain {
appState.progressBar.0 = "Getting update file links ready"
appState.progressBar.1 = 0.1
}
guard let latestRelease = appState.releases.first else { return } guard let latestRelease = appState.releases.first else { return }
guard let asset = latestRelease.assets.first else { return } guard let asset = latestRelease.assets.first else { return }
guard let url = URL(string: asset.url) else { return } guard let url = URL(string: asset.url) else { return }
@@ -138,6 +142,11 @@ func downloadUpdate(appState: AppState) {
request.setValue("application/octet-stream", forHTTPHeaderField: "Accept") request.setValue("application/octet-stream", forHTTPHeaderField: "Accept")
let downloadTask = URLSession.shared.downloadTask(with: request) { localURL, urlResponse, error in let downloadTask = URLSession.shared.downloadTask(with: request) { localURL, urlResponse, error in
updateOnMain {
appState.progressBar.0 = "Downloading update file"
appState.progressBar.1 = 0.2
}
guard let localURL = localURL else { return } guard let localURL = localURL else { return }
let fileManager = FileManager.default let fileManager = FileManager.default
@@ -147,15 +156,21 @@ func downloadUpdate(appState: AppState) {
if fileManager.fileExists(atPath: destinationURL.path) { if fileManager.fileExists(atPath: destinationURL.path) {
try? fileManager.removeItem(at: destinationURL) try? fileManager.removeItem(at: destinationURL)
} }
updateOnMain {
appState.progressBar.0 = "Moving update file to Application Support"
appState.progressBar.1 = 0.4
}
try fileManager.moveItem(at: localURL, to: destinationURL) try fileManager.moveItem(at: localURL, to: destinationURL)
UnzipAndReplace(DownloadedFileURL: destinationURL.path, appState: appState) { UnzipAndReplace(DownloadedFileURL: destinationURL.path, appState: appState)
updateOnMain {
// Restart App updateOnMain {
relaunchApp(afterDelay: 1) appState.progressBar.0 = "Done, please restart!"
} appState.progressBar.1 = 1.0
} }
} catch { } catch {
print("Error moving downloaded file: \(error.localizedDescription)") print("Error moving downloaded file: \(error.localizedDescription)")
} }
@@ -164,28 +179,61 @@ func downloadUpdate(appState: AppState) {
downloadTask.resume() downloadTask.resume()
} }
func UnzipAndReplace(DownloadedFileURL fileURL: String, appState: AppState, completion: @escaping () -> Void = {}) { func UnzipAndReplace(DownloadedFileURL fileURL: String, appState: AppState) {
let appDirectory = Bundle.main.bundleURL.deletingLastPathComponent().path let appDirectory = Bundle.main.bundleURL.deletingLastPathComponent().path
let appBundle = Bundle.main.bundleURL.path let appBundle = Bundle.main.bundleURL.path
let fileManager = FileManager.default let fileManager = FileManager.default
do { do {
updateOnMain {
appState.progressBar.0 = "Deleting existing application"
appState.progressBar.1 = 0.5
}
// Remove the old version of your app // Remove the old version of your app
try fileManager.removeItem(atPath: appBundle) try fileManager.removeItem(atPath: appBundle)
updateOnMain {
appState.progressBar.0 = "Unziping new update file to original Pearcleaner location"
appState.progressBar.1 = 0.6
}
// Unzip the downloaded update file to your app's bundle path // Unzip the downloaded update file to your app's bundle path
let process = Process() let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip") // process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip")
process.arguments = [fileURL, "-d", appDirectory] // process.arguments = [fileURL, "-d", appDirectory]
process.executableURL = URL(fileURLWithPath: "/usr/bin/ditto")
process.arguments = ["-xk", fileURL, appDirectory]
// let pipe = Pipe()
process.standardOutput = FileHandle.nullDevice
process.standardError = FileHandle.nullDevice
try process.run() try process.run()
process.waitUntilExit() process.waitUntilExit()
// let outputData = pipe.fileHandleForReading.readDataToEndOfFile()
// let output = String(decoding: outputData, as: UTF8.self)
//
// writeLog(string: output)
updateOnMain {
appState.progressBar.0 = "Deleting update file"
appState.progressBar.1 = 0.8
}
// After unzipping, remove the update file // After unzipping, remove the update file
try fileManager.removeItem(atPath: fileURL) try fileManager.removeItem(atPath: fileURL)
// Show Restart dialog
// updateOnMain {
// appState.alertType = .restartApp
// appState.showAlert = true
// }
} catch { } catch {
print("Error updating the app: \(error)") print("Error updating the app: \(error)")
} }
completion()
} }
+32 -1
View File
@@ -10,8 +10,12 @@ import SwiftUI
struct AppListItems: View { struct AppListItems: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
// @Binding var reload: Bool
// @Binding var search: String
@State private var isHovered = false @State private var isHovered = false
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@AppStorage("settings.general.mini") private var mini: Bool = false
let appInfo: AppInfo let appInfo: AppInfo
var isSelected: Bool { var isSelected: Bool {
appState.appInfo == appInfo appState.appInfo == appInfo
@@ -39,6 +43,16 @@ struct AppListItems: View {
.truncationMode(.tail) .truncationMode(.tail)
.padding(.horizontal, 5) .padding(.horizontal, 5)
} }
if appInfo.webApp {
Text("web")
.font(.footnote)
.foregroundStyle(Color("mode").opacity(0.5))
.frame(minWidth: 30, minHeight: 15)
.padding(2)
.background(Color("mode").opacity(0.1))
.clipShape(.capsule)
}
Spacer() Spacer()
Text(appInfo.appVersion) Text(appInfo.appVersion)
@@ -82,9 +96,26 @@ struct AppListItems: View {
appState.appInfo = appInfo appState.appInfo = appInfo
findPathsForApp(appState: appState, appInfo: appState.appInfo) findPathsForApp(appState: appState, appInfo: appState.appInfo)
withAnimation(Animation.easeIn(duration: 0.4)) { withAnimation(Animation.easeIn(duration: 0.4)) {
appState.currentView = .files if mini {
appState.showPopover.toggle()
} else {
appState.currentView = .files
}
} }
} }
} }
// .popover(isPresented: $appState.showPopover, arrowEdge: .trailing) {
// VStack {
// FilesView()
// .id(appState.appInfo.id)
// }
// .background(
// Rectangle()
// .background(colorScheme == .dark ? .black.opacity(0.8) : .white)
// .padding(-80)
// )
// .frame(minWidth: 500, minHeight: 500)
//
// }
} }
} }
+9 -6
View File
@@ -47,14 +47,14 @@ struct AppListView: View {
ProgressView("Refreshing applications") ProgressView("Refreshing applications")
Spacer() Spacer()
} }
.frame(width: 300) .frame(width: 250)
.padding(.vertical) .padding(.vertical)
} else { } else {
VStack(alignment: .center) { VStack(alignment: .center) {
VStack(alignment: .center, spacing: 20) { VStack(alignment: .center, spacing: 20) {
HStack { HStack {
SearchBar(search: $search) SearchBar(search: $search, reload: $reload)
Button("") { Button("") {
withAnimation(.easeInOut(duration: 0.5)) { withAnimation(.easeInOut(duration: 0.5)) {
@@ -89,7 +89,7 @@ struct AppListView: View {
Divider().padding(.horizontal, 5) Divider().padding(.horizontal, 5)
} }
} }
.padding(.bottom) // .padding(.bottom)
} }
} }
@@ -113,7 +113,7 @@ struct AppListView: View {
.scrollIndicators(.never) .scrollIndicators(.never)
} }
.frame(width: 300) .frame(width: 250)
.padding(.vertical) .padding(.vertical)
} }
@@ -200,11 +200,12 @@ struct AppDetailsEmptyView: View {
struct SearchBar: View { struct SearchBar: View {
@Binding var search: String @Binding var search: String
@Binding var reload: Bool
var body: some View { var body: some View {
HStack { HStack {
TextField("Search", text: $search) TextField("Search", text: $search)
.textFieldStyle(SimpleSearchStyle(icon: Image(systemName: "magnifyingglass"),trash: true, text: $search)) .textFieldStyle(SimpleSearchStyle(icon: Image(systemName: "magnifyingglass"),trash: true, reload: $reload, text: $search))
} }
.frame(height: 30) .frame(height: 30)
} }
@@ -220,11 +221,13 @@ struct Header: View {
Text(title).opacity(0.5) Text(title).opacity(0.5)
Spacer() Spacer()
Text("\(count)") Text("\(count)")
.frame(minWidth: 30, minHeight: 15) .font(.system(size: 10))
.frame(minWidth: count > 99 ? 30 : 20, minHeight: 15)
.padding(2) .padding(2)
.background(Color("mode").opacity(0.1)) .background(Color("mode").opacity(0.1))
.clipShape(.capsule) .clipShape(.capsule)
// .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous)) // .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
} }
.padding(4)
} }
} }
-42
View File
@@ -1,42 +0,0 @@
////
//// ContentView.swift
//// Pearcleaner
////
//// Created by Alin Lupascu on 10/31/23.
////
//
//import SwiftUI
//
//struct ContentView: View {
// @EnvironmentObject var appState: AppState
// @State private var search = ""
//
// var body: some View {
//
// VStack(alignment: .leading) {
//// AppListNav(search: $search)
// AppListH(search: $search)
//// HStack {
//// Image(nsImage: NSApp.applicationIconImage)
//// .imageScale(.large)
////
//// Text("Pearcleaner")
//// .font(.custom("MontserratAlt1-Light", size: 30, relativeTo: .body))
//// }
//// .padding()
//
//
//
//// VStack() {
////// AppListView(search: $search)
////// AppListNav(search: $search)
//// }
// }
//// .sheet(isPresented: $appState.showFiles, content: {
//// FilesView()
//// })
// .alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) }
//
// }
//}
//
+7 -5
View File
@@ -16,7 +16,7 @@ struct DropTarget: View, DropDelegate {
@State private var shouldFlash = false @State private var shouldFlash = false
private var types: [UTType] = [.fileURL] private var types: [UTType] = [.fileURL]
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@State private var phase: CGFloat = 0 @State private var phase: CGFloat = 4
public init(appState: AppState) { public init(appState: AppState) {
self.appState = appState self.appState = appState
@@ -39,7 +39,9 @@ struct DropTarget: View, DropDelegate {
Image(mini ? "pear" : "logo") Image(mini ? "pear" : "logo")
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: mini ? 104 : 500) .frame(minWidth: mini ? 104 : 300)
.frame(maxWidth: mini ? 104 : 500)
.padding(mini ? 20 : 10)
) )
} }
@@ -52,17 +54,17 @@ struct DropTarget: View, DropDelegate {
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.background { .background {
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 8)
.strokeBorder(LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing), style: StrokeStyle(lineWidth: 3, dash: [10], dashPhase: phase)) .strokeBorder(LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing), style: StrokeStyle(lineWidth: 3, dash: [11.0], dashPhase: phase))
.onAppear { .onAppear {
withAnimation(.linear.speed(0.5).repeat(while: ants, autoreverses: false)) { withAnimation(.linear.speed(0.5).repeat(while: ants, autoreverses: false)) {
if ants { if ants {
phase -= 20 phase -= 22
} }
} }
} }
.onChange(of: ants) { newValue in .onChange(of: ants) { newValue in
withAnimation(.linear.speed(0.5).repeat(while: newValue, autoreverses: false)) { withAnimation(.linear.speed(0.5).repeat(while: newValue, autoreverses: false)) {
phase -= 20 phase -= 22
} }
} }
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
+20 -157
View File
@@ -25,6 +25,7 @@ struct FilesView: View {
ProgressView("Finding application files..") ProgressView("Finding application files..")
Spacer() Spacer()
} }
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(.opacity) .transition(.opacity)
} else { } else {
VStack() { VStack() {
@@ -45,19 +46,19 @@ struct FilesView: View {
HStack(alignment: .center) { HStack(alignment: .center) {
VStack(alignment: .leading, spacing: 5){ VStack(alignment: .leading, spacing: 5){
HStack { HStack {
Text("\(appState.appInfo.appName)").font(mini ? .title2 :.title).fontWeight(.bold) Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold)
// .foregroundStyle(Color("AccentColor")) // .foregroundStyle(Color("AccentColor"))
Text("").foregroundStyle(Color("AccentColor")) Text("").foregroundStyle(Color("AccentColor"))
Text("\(appState.appInfo.appVersion)").font(.title3) Text("\(appState.appInfo.appVersion)").font(.title3)
// .foregroundStyle(.gray.opacity(0.8)) // .foregroundStyle(.gray.opacity(0.8))
} }
Text("\(appState.appInfo.bundleIdentifier)").font(mini ? .footnote :.title3) Text("\(appState.appInfo.bundleIdentifier)").font(.title3)
.foregroundStyle((.gray.opacity(0.8))) .foregroundStyle((.gray.opacity(0.8)))
} }
Spacer() Spacer()
Text("\(appSize)").font(mini ? .title2 :.title).fontWeight(.bold) Text("\(appSize)").font(.title).fontWeight(.bold)
// .foregroundStyle(Color("AccentColor")) // .foregroundStyle(Color("AccentColor"))
} }
@@ -74,6 +75,19 @@ struct FilesView: View {
// .foregroundStyle(Color("AccentColor").opacity(0.7)) // .foregroundStyle(Color("AccentColor").opacity(0.7))
.underline() .underline()
} }
if appState.appInfo.webApp {
HStack {
Text("web")
.font(.footnote)
.foregroundStyle(Color("mode").opacity(0.5))
.frame(minWidth: 30, minHeight: 15)
.padding(2)
.background(Color("mode").opacity(0.1))
.clipShape(.capsule)
Spacer()
}
}
if appState.appInfo.appName.count < 5 { if appState.appInfo.appName.count < 5 {
HStack(alignment: .center) { HStack(alignment: .center) {
Button(""){ Button(""){
@@ -103,7 +117,7 @@ struct FilesView: View {
} }
.padding(20) .padding(20)
} }
.padding([.horizontal, .bottom]) // .padding(.horizontal)
.background( .background(
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 8)
// .strokeBorder(Color("AccentColor"), lineWidth: 0.5) // .strokeBorder(Color("AccentColor"), lineWidth: 0.5)
@@ -141,17 +155,7 @@ struct FilesView: View {
HStack() { HStack() {
Spacer() Spacer()
// if appState.trashLaunch {
// Button("Close") {
// Task {
// NSApp.terminate(nil)
// exit(0)
// }
// }
// .buttonStyle(WindowActionButton(action: .cancel))
// }
Button("Uninstall") { Button("Uninstall") {
Task { Task {
@@ -182,7 +186,7 @@ struct FilesView: View {
} }
} }
// .frame(minWidth: 700) // .frame(minWidth: 500, minHeight: 500)
.onAppear { .onAppear {
Task { Task {
calculateFileDetails() calculateFileDetails()
@@ -302,144 +306,3 @@ struct FileDetailsItem: View {
) )
} }
} }
func writeLog(string: String) {
let fileManager = FileManager.default
let home = fileManager.homeDirectoryForCurrentUser.path
let logFilePath = "\(home)/Downloads/log.txt"
// Check if the log file exists, and create it if it doesn't
if !fileManager.fileExists(atPath: logFilePath) {
if !fileManager.createFile(atPath: logFilePath, contents: nil, attributes: nil) {
print("Failed to create the log file.")
return
}
}
do {
if let fileHandle = FileHandle(forWritingAtPath: logFilePath) {
let ns = "\(string)\n"
fileHandle.seekToEndOfFile()
fileHandle.write(ns.data(using: .utf8)!)
fileHandle.closeFile()
} else {
print("Error opening file for appending")
}
}
}
//struct FilesView: View {
// @EnvironmentObject var appState: AppState
// @State private var appSize: String = ""
// @State private var showDetails: Bool = false
// @State private var itemDetails: [(size: String, icon: Image?)] = []
//
// var body: some View {
// VStack(alignment: .center) {
// if !self.showDetails {
// ProgressView("Finding application files..")
// } else {
// VStack() {
// HStack() {
// Text("\(appState.paths.count > 1 ? "\(appState.paths.count) files found" : "\(appState.paths.count) file found")")
// Text("")
// Text("\(appSize)")
// }
// .padding()
//
// if appState.appInfo.appName.count < 5 {
// Text("Short or common word app name, there might be unrelated files found")
// .font(.footnote)
// .foregroundStyle(.red)
// }
//
// ScrollView {
// ForEach(Array(zip(appState.paths, itemDetails)), id: \.0) { path, details in
// FileDetailsItem(size: details.size, icon: details.icon, path: path)
// }
// }
// .frame(width: 500)
//
// Button("Remove") {
// Task {
// updateOnMain {
// appState.appInfo = AppInfo.empty
// }
// let selectedItemsArray = Array(appState.selectedItems)
// killApp(appId: appState.appInfo.bundleIdentifier) {
// moveFilesToTrash(at: selectedItemsArray) {
// refreshAppList(appState.appInfo)
// }
// }
// }
// }
// .disabled(appState.selectedItems.isEmpty)
// }
// .padding()
//
// }
//
// }
// .frame(minWidth: 500)
// .onAppear {
// Task {
// // appState.appInfo = appInfo
// // findPathsForApp(appState: appState, appInfo: appState.appInfo)
// calculateFileDetails()
// }
// }
// }
//
//
// func calculateFileDetails() {
// if appState.paths.count != 0 {
// itemDetails = Array(repeating: (size: "", icon: nil), count: appState.paths.count)
//
// Task {
// for (index, path) in appState.paths.enumerated() {
// var size = ""
// var icon: Image? = nil
//
// if let appSize = totalSizeOnDisk(for: path) {
// size = "\(appSize)"
// } else {
// print("Error calculating the total size on disk for item \(index).")
// }
//
// if let folderIcon = getIconForFileOrFolder(atPath: path) {
// icon = folderIcon
// }
//
// itemDetails[index] = (size: size, icon: icon)
//
//
//
// }
//
// if let appSize = totalSizeOnDisk(for: appState.paths) {
// self.appSize = "\(appSize)"
// // updateOnMain {
// // appState.doneLoading = true
// // }
// self.showDetails = true
// // appState.doneLoading = true
// } else {
// print("Error calculating the total size on disk.")
// }
// }
// } else {
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// calculateFileDetails()
// }
// }
// }
//
// func refreshAppList(_ appInfo: AppInfo) {
// let sortedApps = getSortedApps()
// appState.sortedApps.userApps = []
// appState.sortedApps.systemApps = []
// appState.sortedApps.userApps = sortedApps.userApps
// appState.sortedApps.systemApps = sortedApps.systemApps
// }
//}
+29 -17
View File
@@ -15,7 +15,8 @@ struct MiniMode: View {
@State private var showSys: Bool = true @State private var showSys: Bool = true
@State private var showUsr: Bool = true @State private var showUsr: Bool = true
@State private var reload: Bool = false @State private var reload: Bool = false
@AppStorage("settings.general.glass") private var glass: Bool = false
var body: some View { var body: some View {
@@ -26,7 +27,7 @@ struct MiniMode: View {
VStack(spacing: 0) { VStack(spacing: 0) {
if appState.currentView == .empty { if appState.currentView == .empty {
TopBarMini(reload: $reload, search: $search) TopBarMini(reload: $reload, search: $search)
AppDetailsEmptyView() MiniEmptyView()
} else if appState.currentView == .files { } else if appState.currentView == .files {
TopBarMini(reload: $reload, search: $search) TopBarMini(reload: $reload, search: $search)
FilesView() FilesView()
@@ -39,8 +40,11 @@ struct MiniMode: View {
// .padding(.leading, appState.sidebar ? 0 : 10) // .padding(.leading, appState.sidebar ? 0 : 10)
.transition(.move(edge: .leading)) .transition(.move(edge: .leading))
} }
.frame(minWidth: 300, minHeight: 300)
.edgesIgnoringSafeArea(.all) .edgesIgnoringSafeArea(.all)
.background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil)
.onOpenURL(perform: { url in .onOpenURL(perform: { url in
let deeplinkManager = DeeplinkManager() let deeplinkManager = DeeplinkManager()
deeplinkManager.manage(url: url, appState: appState) deeplinkManager.manage(url: url, appState: appState)
@@ -70,6 +74,11 @@ struct MiniEmptyView: View {
DropTarget(appState: appState) DropTarget(appState: appState)
.frame(maxWidth: mini ? 300 : 500) .frame(maxWidth: mini ? 300 : 500)
Text("Drop an app to begin")
.font(.title3)
.padding(.bottom, 25)
.opacity(0.5)
Spacer() Spacer()
if appState.isReminderVisible { if appState.isReminderVisible {
@@ -87,11 +96,9 @@ struct MiniEmptyView: View {
Spacer() Spacer()
} }
Text("Drop an app above or select one from the list to begin")
.font(.title3)
.padding(.bottom, 25)
.opacity(0.5)
} }
// .padding()
} }
} }
@@ -140,14 +147,6 @@ struct MiniAppView: View {
} else { } else {
VStack(alignment: .center) { VStack(alignment: .center) {
// VStack(alignment: .center, spacing: 20) {
// HStack {
// SearchBar(search: $search)
// }
// }
// .padding(.horizontal, 5)
// .padding(.bottom)
ScrollView { ScrollView {
VStack(alignment: .leading) { VStack(alignment: .leading) {
@@ -161,7 +160,6 @@ struct MiniAppView: View {
Divider().padding(.horizontal, 5) Divider().padding(.horizontal, 5)
} }
} }
.padding(.bottom)
} }
} }
@@ -183,15 +181,29 @@ struct MiniAppView: View {
} }
.scrollIndicators(.never) .scrollIndicators(.never)
} }
.padding(.vertical) // .padding(.vertical)
} }
} }
} }
.transition(.move(edge: .leading)) .transition(.move(edge: .leading))
.popover(isPresented: $appState.showPopover, arrowEdge: .trailing) {
VStack {
FilesView()
.id(appState.appInfo.id)
}
.background(
Rectangle()
.fill(Color("pop"))
// .background(Color("pop"))
.padding(-80)
)
.frame(minWidth: 600, minHeight: 500)
}
} }
} }
+40 -49
View File
@@ -18,20 +18,45 @@ struct TopBarMini: View {
var body: some View { var body: some View {
HStack(alignment: .center, spacing: 5) { HStack(alignment: .center, spacing: 5) {
Spacer() Spacer()
if appState.currentView == .apps { if appState.currentView == .apps {
HStack(alignment: .center, spacing: 0) { // HStack {
Button("") { // Spacer()
withAnimation(.easeInOut(duration: 0.5)) {
appState.currentView = .empty
appState.appInfo = AppInfo.empty
}
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode")))
SearchBarMini(search: $search, reload: $reload)
.frame(width: 180)
// .padding(.top, 5)
// Spacer()
// Button("") {
// withAnimation(.easeInOut(duration: 0.5)) {
// // Refresh Apps list
// reload.toggle()
// let sortedApps = getSortedApps()
// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// appState.sortedApps.userApps = sortedApps.userApps
// appState.sortedApps.systemApps = sortedApps.systemApps
// reload.toggle()
// }
//
// }
// }
// .buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh app list", color: Color("mode")))
// .padding(.leading, 5)
// }
}
if appState.currentView == .apps {
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
appState.currentView = .empty
appState.appInfo = AppInfo.empty
}
} }
.buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode")))
} else if appState.currentView == .empty { } else if appState.currentView == .empty {
Button("") { Button("") {
withAnimation(.easeInOut(duration: 0.5)) { withAnimation(.easeInOut(duration: 0.5)) {
@@ -55,56 +80,22 @@ struct TopBarMini: View {
.buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps", color: Color("mode"))) .buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps", color: Color("mode")))
} }
if appState.currentView == .apps {
HStack {
Spacer()
SearchBarMini(search: $search)
Spacer()
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
// Refresh Apps list
reload.toggle()
let sortedApps = getSortedApps()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.sortedApps.userApps = sortedApps.userApps
appState.sortedApps.systemApps = sortedApps.systemApps
reload.toggle()
}
}
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh app list", color: Color("mode")))
}
}
Spacer()
// if sentinel {
// Button("") {
// //
// }
// .buttonStyle(SimpleButtonStyle(icon: "lock.shield", help: "Sentinel enabled", color: .green, shield: true))
// }
} }
.edgesIgnoringSafeArea(.all) .padding(.horizontal, 10)
.padding(.horizontal) .padding(.top, 10)
.padding(.top, 30) .padding(.bottom, 15)
} }
} }
struct SearchBarMini: View { struct SearchBarMini: View {
@Binding var search: String @Binding var search: String
@Binding var reload: Bool
var body: some View { var body: some View {
HStack { HStack {
TextField("Search", text: $search) TextField("Search", text: $search)
.textFieldStyle(SimpleSearchStyle(trash: true, text: $search)) .textFieldStyle(SimpleSearchStyle(trash: true, reload: $reload, text: $search))
} }
.frame(height: 30) .frame(height: 30)
} }
-19
View File
@@ -1,19 +0,0 @@
//
// TrashView.swift
// Pearcleaner
//
// Created by Alin Lupascu on 11/11/23.
//
import Foundation
import SwiftUI
struct TrashView: View {
@EnvironmentObject var appState: AppState
var body: some View {
VStack() {
Text("Trash view my boi")
}
}
}
+89
View File
@@ -0,0 +1,89 @@
//
// NewWin.swift
// Pearcleaner
//
// Created by Alin Lupascu on 11/16/23.
//
import AppKit
import SwiftUI
class NewWin: NSWindowController {
static var shared = NewWin()
static func show(appState: AppState, width: CGFloat, height: CGFloat, newWin: NewWindow) {
shared = NewWin(appState: appState, width: width, height: height, newWin: newWin)
shared.window?.makeKeyAndOrderFront(nil)
}
static func close() {
shared.window?.close()
}
convenience init(appState: AppState, width: CGFloat, height: CGFloat, newWin: NewWindow) {
let window = Self.makeWindow(width: width, height: height)
window.backgroundColor = NSColor.controlBackgroundColor
self.init(window: window)
// Using Visual Effect to make titlebar fully transparent
let visualEffect = NSVisualEffectView()
visualEffect.blendingMode = .behindWindow
visualEffect.state = .active
visualEffect.material = .underWindowBackground
let contentView = makeNewView(appState: appState, newWin: newWin)
let hostView = NSHostingView(rootView: contentView)
window.contentView = visualEffect
visualEffect.addSubview(hostView)
hostView.frame = visualEffect.frame
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
window.center()
window.title = "Support Admin"
}
private static func makeWindow(width: CGFloat, height: CGFloat) -> NSWindow {
let contentRect = NSRect(x: 0, y: 0, width: width, height: height)
let styleMask: NSWindow.StyleMask = [
.titled,
.fullSizeContentView,
// .closable,
]
return NSWindow(contentRect: contentRect,
styleMask: styleMask,
backing: .buffered,
defer: false)
}
private func makeNewView(appState: AppState, newWin: NewWindow) -> some View {
switch newWin {
case .update:
return AnyView(UpdateView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.environmentObject(appState))
case .no_update:
return AnyView(NoUpdateView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.environmentObject(appState))
case .perm:
return AnyView(PermView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.environmentObject(appState))
// case .restart:
// return AnyView(RestartView()
// .frame(maxWidth: .infinity, maxHeight: .infinity)
// .environmentObject(appState))
}
}
}
+92
View File
@@ -0,0 +1,92 @@
//
// PermView.swift
// Pearcleaner
//
// Created by Alin Lupascu on 11/16/23.
//
import SwiftUI
struct PermView: View {
@EnvironmentObject var appState: AppState
var body: some View {
VStack {
HStack {
Spacer()
Text("Attention")
.font(.title)
.bold()
.padding(.vertical)
Spacer()
}
.background(.red)
Text("Pearcleaner requires the following permissions:")
.font(.title3)
.fontWeight(.semibold)
// .padding(.horizontal, 20)
.padding(.vertical)
// .padding(.bottom)
ScrollView {
Text(" - Full Disk Access permission to find and delete files in non-user locations.\n - Accessibility permission to delete files via Finder which allows for the Undo function.\n\nAdd Pearcleaner in both Privacy panes via the + or by dragging the app over the pane. If the app is already pre-populated in the list, just toggle On. Restart app when both permissions are granted.")
.font(.body)
.multilineTextAlignment(.leading)
.padding(.horizontal, 20)
}
.frame(height: 130)
.padding(.horizontal)
Spacer()
HStack {
Button(action: {
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") {
NSWorkspace.shared.open(url)
}
}) {
Text("Full Disk Access")
}
.buttonStyle(WindowActionButton(action: .accept))
.padding()
Button(action: {
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") {
NSWorkspace.shared.open(url)
}
}) {
Text("Accessibility")
}
.buttonStyle(WindowActionButton(action: .accept))
.padding()
Button(action: {
// NewWin.close
relaunchApp(afterDelay: 1)
}) {
Text("Restart")
}
.buttonStyle(WindowActionButton(action: .cancel))
.padding()
}
}
.padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0))
}
}
+185
View File
@@ -0,0 +1,185 @@
//
// UpdateView.swift
// Pearcleaner
//
// Created by Alin Lupascu on 11/16/23.
//
import SwiftUI
struct UpdateView: View {
@EnvironmentObject var appState: AppState
var body: some View {
VStack {
HStack {
Spacer()
.frame(width: appState.progressBar.1 != 1.0 ? 150 : 180)
Text("\(appState.progressBar.1 != 1.0 ? "Update Available 🥳" : "Completed 🚀")")
.font(.title)
.bold()
.padding(.vertical)
Spacer()
Text("v\(appState.releases.first?.tag_name ?? "")")
.font(.title3)
.fontWeight(.semibold)
.foregroundStyle(Color.white.opacity(0.5))
.padding()
}
.background(.blue)
Spacer()
ScrollView {
Text("\(appState.releases.first?.body ?? "No release information")")
.font(.body)
.multilineTextAlignment(.leading)
.padding(.horizontal, 20)
}
.frame(height: 160)
.padding()
Spacer()
VStack() {
ProgressView("\(appState.progressBar.0)", value: appState.progressBar.1, total: 1.0)
.progressViewStyle(LinearProgressViewStyle(tint: .blue))
.frame(height: 10)
}
.padding()
HStack(alignment: .center, spacing: 20) {
Button(action: NewWin.close) {
Text("Cancel")
}
.buttonStyle(WindowActionButton(action: .cancel))
if appState.progressBar.1 != 1.0 {
Button(action: {
downloadUpdate(appState: appState)
}) {
Text("Update")
}
.buttonStyle(WindowActionButton(action: .accept))
} else {
Button(action: {
NewWin.close()
relaunchApp(afterDelay: 1)
}) {
Text("Restart")
}
.buttonStyle(WindowActionButton(action: .accept))
}
}
.padding(.bottom)
}
.padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0))
}
}
struct NoUpdateView: View {
@EnvironmentObject var appState: AppState
var body: some View {
VStack {
HStack {
Spacer()
.frame(width: appState.progressBar.1 != 1.0 ? 140 : 180)
Text("\(appState.progressBar.1 != 1.0 ? "No Update Available 😌" : "Completed 🚀")")
.font(.title)
.bold()
.padding(.vertical)
Spacer()
Text("v\(appState.releases.first?.tag_name ?? "")")
.font(.title3)
.fontWeight(.semibold)
.foregroundStyle(Color.white.opacity(0.5))
.padding()
}
.background(.blue)
Spacer()
Text("Pearcleaner is on the most current release available, but you may force a re-download of the same version below.")
.font(.body)
.lineLimit(3)
.multilineTextAlignment(.leading)
.padding(20)
Spacer()
VStack() {
ProgressView("\(appState.progressBar.0)", value: appState.progressBar.1, total: 1.0)
.progressViewStyle(LinearProgressViewStyle(tint: .blue))
.frame(height: 10)
}
.padding()
HStack(alignment: .center, spacing: 20) {
if appState.progressBar.1 < 0.1 {
Button(action: {
downloadUpdate(appState: appState)
}) {
Text("Force Update")
}
.buttonStyle(WindowActionButton(action: .cancel))
Button(action: {
NewWin.close()
}) {
Text("Okay")
}
.buttonStyle(WindowActionButton(action: .accept))
}
if appState.progressBar.1 == 1.0 {
Button(action: {
NewWin.close()
relaunchApp()
}) {
Text("Restart")
}
.buttonStyle(WindowActionButton(action: .accept))
}
}
.padding(.bottom)
}
.padding(EdgeInsets(top: -25, leading: 0, bottom: 25, trailing: 0))
}
}