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 */
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A272B03E71D00376976 /* MiniMode.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 */; };
C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; };
C7575D382B0181FC006A600A /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7575D372B0181FC006A600A /* FileWatcher */; };
@@ -19,7 +22,6 @@
C76D08512AF850F300D07867 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08502AF850F300D07867 /* About.swift */; };
C76D08552AF89CDE00D07867 /* AppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08542AF89CDE00D07867 /* AppListView.swift */; };
C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */; };
C77B90062AF18E2E009CC655 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90052AF18E2E009CC655 /* ContentView.swift */; };
C77B90082AF18E2F009CC655 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C77B90072AF18E2F009CC655 /* Assets.xcassets */; };
C77B90162AF19377009CC655 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90152AF19377009CC655 /* AppState.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 */; };
C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D492AFEB26700C7ED9E /* AppListItems.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 */; };
C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D31D582B00A49300C7ED9E /* DropTarget.swift */; };
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; };
@@ -63,18 +64,19 @@
/* Begin PBXFileReference section */
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>"; };
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>"; };
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>"; };
C76D08472AF83C3F00D07867 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = "<group>"; };
C76D08492AF83C6E00D07867 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = "<group>"; };
C76D084B2AF83C9D00D07867 /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; };
C76D084D2AF84D8C00D07867 /* Builds */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Builds; sourceTree = "<group>"; };
C76D08502AF850F300D07867 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
C76D08542AF89CDE00D07867 /* AppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListView.swift; sourceTree = "<group>"; };
C77B90002AF18E2E009CC655 /* Pearcleaner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pearcleaner.app; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
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>"; };
@@ -90,7 +92,6 @@
C7D31D472AFEB23700C7ED9E /* TopBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBar.swift; sourceTree = "<group>"; };
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListItems.swift; sourceTree = "<group>"; };
C7D31D502AFF00F300C7ED9E /* Locations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locations.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
@@ -115,6 +116,16 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
C7045A302B068ABE00376976 /* Windows */ = {
isa = PBXGroup;
children = (
C7045A312B068AD700376976 /* NewWin.swift */,
C7045A332B068B2900376976 /* UpdateView.swift */,
C7045A352B068DD700376976 /* PermView.swift */,
);
path = Windows;
sourceTree = "<group>";
};
C72893072AFD442600C8C1CD /* Logic */ = {
isa = PBXGroup;
children = (
@@ -174,7 +185,6 @@
C77B8FF72AF18E2E009CC655 = {
isa = PBXGroup;
children = (
C76D084D2AF84D8C00D07867 /* Builds */,
C77B90022AF18E2E009CC655 /* Pearcleaner */,
C72893102AFD51EA00C8C1CD /* PearcleanerSentinel */,
C77B90012AF18E2E009CC655 /* Products */,
@@ -198,6 +208,7 @@
C76D084F2AF84E0100D07867 /* Resources */,
C76D08462AF83C2F00D07867 /* Settings */,
C77B901F2AF1B390009CC655 /* Views */,
C7045A302B068ABE00376976 /* Windows */,
C72893072AFD442600C8C1CD /* Logic */,
);
path = Pearcleaner;
@@ -206,13 +217,11 @@
C77B901F2AF1B390009CC655 /* Views */ = {
isa = PBXGroup;
children = (
C77B90052AF18E2E009CC655 /* ContentView.swift */,
C77B90222AF2D616009CC655 /* FilesView.swift */,
C76D08542AF89CDE00D07867 /* AppListView.swift */,
C7D31D472AFEB23700C7ED9E /* TopBar.swift */,
C7045A292B03FAF000376976 /* TopBarMini.swift */,
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
C7D31D522B0048BE00C7ED9E /* TrashView.swift */,
C7D31D582B00A49300C7ED9E /* DropTarget.swift */,
C7045A272B03E71D00376976 /* MiniMode.swift */,
);
@@ -335,13 +344,13 @@
buildActionMask = 2147483647;
files = (
C76D08482AF83C3F00D07867 /* Update.swift in Sources */,
C7045A342B068B2900376976 /* UpdateView.swift in Sources */,
C77B90162AF19377009CC655 /* AppState.swift in Sources */,
C7045A322B068AD700376976 /* NewWin.swift in Sources */,
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */,
C77B901A2AF1938F009CC655 /* Logic.swift in Sources */,
C7D31D532B0048BE00C7ED9E /* TrashView.swift in Sources */,
C7D31D462AFDA90C00C7ED9E /* Sentinel.swift in Sources */,
C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */,
C77B90062AF18E2E009CC655 /* ContentView.swift in Sources */,
C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */,
C77B901C2AF193A3009CC655 /* Styles.swift in Sources */,
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */,
@@ -350,6 +359,7 @@
C76D084A2AF83C6E00D07867 /* General.swift in Sources */,
C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */,
C7D31D552B008CB900C7ED9E /* Shapes.swift in Sources */,
C7045A362B068DD700376976 /* PermView.swift in Sources */,
C76D08512AF850F300D07867 /* About.swift in Sources */,
C77B90182AF19382009CC655 /* AppCommands.swift in Sources */,
C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */,
@@ -525,7 +535,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES;
@@ -534,15 +544,15 @@
INFOPLIST_FILE = Pearcleaner/Resources/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Pearcleaner;
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_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 = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.4;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -559,7 +569,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES;
@@ -568,15 +578,15 @@
INFOPLIST_FILE = Pearcleaner/Resources/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Pearcleaner;
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_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 = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.4;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
+1 -1
View File
@@ -23,7 +23,7 @@ struct AppCommands: Commands {
Button {
loadGithubReleases(appState: appState, manual: true)
} label: {
Text("Updates")
Text("Check for Updates")
}
.keyboardShortcut("u", modifiers: .command)
+14 -7
View File
@@ -22,8 +22,11 @@ class AppState: ObservableObject
// @Published var currentMode = CurrentMode.regular
@Published var showAlert: Bool = false
@Published var sidebar: Bool = true
@Published var showPopover: Bool = false
@Published var isReminderVisible: Bool = false
@Published var releases = [Release]()
@Published var progressBar: (String, Double) = ("Ready", 0.0)
//Window
// @Published var winWidth: CGFloat = 1020
@@ -35,7 +38,8 @@ class AppState: ObservableObject
bundleIdentifier: "",
appName: "",
appVersion: "",
appIcon: nil
appIcon: nil,
webApp: false
)
}
}
@@ -47,8 +51,9 @@ struct AppInfo: Identifiable, Hashable {
let appName: String
let appVersion: String
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
}
//enum CurrentMode:Int
//{
// case regular
// case mini
//}
enum NewWindow:Int
{
case update
case no_update
case perm
}
enum AlertType:Int
{
+34 -2
View File
@@ -77,6 +77,7 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String {
var appIcon: NSImage?
var appName: String?
var webApp: Bool?
if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String {
appName = localizedName
} 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)")
}
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 {
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)
collection.insert(url, at: 0)
}
// Only search for extra files if not a webapp
// if !appInfo.webApp {
let fileManager = FileManager.default
let dispatchGroup = DispatchGroup()
let bundleComponents = appInfo.bundleIdentifier.components(separatedBy: ".")
@@ -203,20 +213,32 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) {
if collection.contains(itemURL) {
return
}
// Catch web app plist files
if appInfo.webApp {
if itemL.contains(bundleIdentifierL) {
if collection.contains(itemURL) {
return
}
collection.append(itemURL)
}
} else {
// Catch all the com.apple folders in the OS since there's a ton and probably unrelated
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)
@@ -227,6 +249,8 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) {
}
}
}
}
} catch {
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
+31 -9
View File
@@ -143,7 +143,10 @@ struct SimpleSearchStyle: TextFieldStyle {
@State private var isHovered = false
@State var icon: Image?
@State var trash: Bool = false
@Binding var reload: Bool
@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 {
@@ -156,21 +159,40 @@ struct SimpleSearchStyle: TextFieldStyle {
}
configuration
.frame(maxWidth: 300)
// .frame(minWidth: mini ? 80 : 300)
// .frame(maxWidth: mini ? 150 : 300)
.font(.title3)
.textFieldStyle(PlainTextFieldStyle())
if trash {
Image(systemName: "xmark")
Image(systemName: "arrow.triangle.2.circlepath")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 10, height: 10)
.frame(width: 15, height: 15)
.padding(.trailing, 5)
.foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5))
.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(
!configuration.isPressed ?
!hovered ?
action == .accept ? Color("AccentColor") : Color.red :
action == .accept ? Color("AccentColor").opacity(0.8) : Color.red.opacity(0.8) :
action == .accept ? Color("AccentColor").opacity(0.5) : Color.red.opacity(0.5)
action == .accept ? Color("button") : Color.red :
action == .accept ? Color("button").opacity(0.8) : Color.red.opacity(0.8) :
action == .accept ? Color("button").opacity(0.5) : Color.red.opacity(0.5)
)
.foregroundColor(.white)
.cornerRadius(8)
+122 -23
View File
@@ -25,35 +25,98 @@ func updateOnBackground(_ updates: @escaping () -> Void) {
// Resize window
func resizeWindow() {
func resizeWindow(width: CGFloat, height: CGFloat) {
if let window = NSApplication.shared.windows.first {
let newSize = NSSize(width: 500, height: 450)
let newSize = NSSize(width: width, height: height)
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
func checkAndRequestFullDiskAccess(appState: AppState, skipAlert: Bool = false) -> Bool {
@AppStorage("settings.permissions.disk") var diskP: Bool = false
let fileURL = URL(fileURLWithPath: "\(home)/Library/Application Support/com.apple.TCC/TCC.db")
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 accessStatus = FileManager.default.isWritableFile(atPath: fileURL.path)
if accessStatus {
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 {
diskP = true
_ = checkAndRequestAccessibilityAccess(appState: appState)
return true
} else {
diskP = false
if !skipAlert {
appState.alertType = .diskAccess
appState.showAlert = true
NewWin.show(appState: appState, width: 500, height: 350, newWin: .perm)
// appState.alertType = .diskAccess
// appState.showAlert = true
}
return false
}
// let fileURL = URL(fileURLWithPath: "/Library/Application Support/com.apple.TCC/TCC.db")
//
// let accessStatus = FileManager.default.isReadableFile(atPath: fileURL.path)
// 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
@@ -277,35 +356,28 @@ func presentAlert(appState: AppState) -> Alert {
switch appState.alertType {
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")) {
// downloadUpdate(appState: appState)
launchUpdate()
return Alert(title: Text("Update Available 🥳"), message: Text("You may choose to install the update now, otherwise you may check again later from Settings"), primaryButton: .default(Text("Install")) {
downloadUpdate(appState: appState)
// launchUpdate()
appState.alertType = .off
}, secondaryButton: .cancel())
case .no_update:
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)
appState.alertType = .off
})
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") {
NSWorkspace.shared.open(url)
}
appState.alertType = .off
}, 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:
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
relaunchApp(afterDelay: 1)
})
relaunchApp()
}, secondaryButton: .cancel(Text("Later")))
case .off:
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) {
let cmd = load ? "load" : "unload"
if let plistPath = Bundle.main.path(forResource: "com.alienator88.PearcleanerSentinel", ofType: "plist") {
+10 -51
View File
@@ -15,64 +15,25 @@ struct PearcleanerApp: App {
@AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1
@AppStorage("settings.permissions.disk") private var diskP: 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
@State private var search = ""
var body: some Scene {
WindowGroup {
Group {
if !mini {
AppListView(search: $search)
.environmentObject(appState)
.frame(minWidth: 1020, minHeight: 700)
.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
}
}
.frame(minWidth: 800, minHeight: 500)
} else {
MiniMode(search: $search)
.frame(minWidth: 300, minHeight: 300)
}
}
.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: "*"))
@@ -90,9 +51,6 @@ struct PearcleanerApp: App {
// 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 {
@@ -116,7 +74,7 @@ struct PearcleanerApp: App {
#endif
}
}
}
}
.windowStyle(.hiddenTitleBar)
@@ -125,6 +83,7 @@ struct PearcleanerApp: App {
.commands {
AppCommands(appState: appState)
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>
<key>CFBundleTypeName</key>
<string>Application Bundle</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>LSItemContentTypes</key>
<array>
<string>com.apple.application-bundle</string>
</array>
</dict>
</array>
<key>CFBundleGetInfoString</key>
<string></string>
<key>CFBundleURLTypes</key>
<array>
<dict>
@@ -30,5 +32,13 @@
</array>
</dict>
</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>
</plist>
+1 -1
View File
@@ -139,7 +139,7 @@ struct AboutSettingsTab: View {
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)
.frame(width: 750, height: 325)
+46 -9
View File
@@ -13,7 +13,9 @@ struct GeneralSettingsTab: View {
@AppStorage("settings.general.glass") private var glass: Bool = true
@AppStorage("settings.general.mini") private var mini: Bool = false
@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 {
Form {
@@ -38,19 +40,45 @@ struct GeneralSettingsTab: View {
HStack {
VStack(alignment: .leading, spacing: 5) {
Text("Dark Mode").font(.title2)
Text("Toggles between dark and light mode")
Text("Appearance").font(.title2)
Text("Toggles color mode")
.font(.footnote)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $isDark, label: {
})
.toggleStyle(.switch)
.onChange(of: isDark) { newValue in
displayMode.colorScheme = newValue ? .dark : .light
Picker("", selection: $selectedTheme) {
ForEach(themes, id: \.self) { theme in
Text(theme)
}
}
.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()
.background(
RoundedRectangle(cornerRadius: 8)
@@ -69,7 +97,16 @@ struct GeneralSettingsTab: View {
})
.toggleStyle(.switch)
.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()
+5 -1
View File
@@ -39,13 +39,17 @@ struct SentinelSettingsTab: View {
RoundedRectangle(cornerRadius: 8)
.fill(Color("mode").opacity(0.05))
)
Text("If you uninstall Pearcleaner, make sure to disable Sentinel first").font(.footnote)
.foregroundStyle(Color.red)
.padding()
}
}
.padding(20)
.frame(width: 450, height: 100)
.frame(width: 450, height: 130)
}
+64 -16
View File
@@ -93,6 +93,7 @@ struct UpdateSettingsTab: View {
func loadGithubReleases(appState: AppState, manual: Bool = false) {
let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/releases")!
let request = URLRequest(url: url)
// Set the token for a private repo
// request.setValue("token \(ghToken)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
@@ -114,22 +115,25 @@ func checkForUpdate(appState: AppState, manual: Bool = false) {
guard let latestRelease = appState.releases.first else { return }
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
if latestRelease.tag_name > currentVersion ?? "" {
appState.alertType = .update
appState.showAlert = true
NewWin.show(appState: appState, width: 500, height: 400, newWin: .update)
// appState.alertType = .update
// appState.showAlert = true
} else {
if manual {
appState.alertType = .no_update
appState.showAlert = true
NewWin.show(appState: appState, width: 500, height: 300, newWin: .no_update)
// appState.alertType = .no_update
// appState.showAlert = true
}
}
}
func launchUpdate() {
NSWorkspace.shared.open(URL(string: "https://github.com/alienator88/Pearcleaner/releases")!)
}
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 asset = latestRelease.assets.first 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")
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 }
let fileManager = FileManager.default
@@ -147,15 +156,21 @@ func downloadUpdate(appState: AppState) {
if fileManager.fileExists(atPath: destinationURL.path) {
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)
UnzipAndReplace(DownloadedFileURL: destinationURL.path, appState: appState) {
UnzipAndReplace(DownloadedFileURL: destinationURL.path, appState: appState)
updateOnMain {
// Restart App
relaunchApp(afterDelay: 1)
}
appState.progressBar.0 = "Done, please restart!"
appState.progressBar.1 = 1.0
}
} catch {
print("Error moving downloaded file: \(error.localizedDescription)")
}
@@ -164,28 +179,61 @@ func downloadUpdate(appState: AppState) {
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 appBundle = Bundle.main.bundleURL.path
let fileManager = FileManager.default
do {
updateOnMain {
appState.progressBar.0 = "Deleting existing application"
appState.progressBar.1 = 0.5
}
// Remove the old version of your app
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
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip")
process.arguments = [fileURL, "-d", appDirectory]
// process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip")
// process.arguments = [fileURL, "-d", appDirectory]
process.executableURL = URL(fileURLWithPath: "/usr/bin/ditto")
process.arguments = ["-xk", fileURL, appDirectory]
// let pipe = Pipe()
process.standardOutput = FileHandle.nullDevice
process.standardError = FileHandle.nullDevice
try process.run()
process.waitUntilExit()
// let outputData = pipe.fileHandleForReading.readDataToEndOfFile()
// let output = String(decoding: outputData, as: UTF8.self)
//
// writeLog(string: output)
updateOnMain {
appState.progressBar.0 = "Deleting update file"
appState.progressBar.1 = 0.8
}
// After unzipping, remove the update file
try fileManager.removeItem(atPath: fileURL)
// Show Restart dialog
// updateOnMain {
// appState.alertType = .restartApp
// appState.showAlert = true
// }
} catch {
print("Error updating the app: \(error)")
}
completion()
}
+31
View File
@@ -10,8 +10,12 @@ import SwiftUI
struct AppListItems: View {
@EnvironmentObject var appState: AppState
// @Binding var reload: Bool
// @Binding var search: String
@State private var isHovered = false
@Environment(\.colorScheme) var colorScheme
@AppStorage("settings.general.mini") private var mini: Bool = false
let appInfo: AppInfo
var isSelected: Bool {
appState.appInfo == appInfo
@@ -39,6 +43,16 @@ struct AppListItems: View {
.truncationMode(.tail)
.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()
Text(appInfo.appVersion)
@@ -82,9 +96,26 @@ struct AppListItems: View {
appState.appInfo = appInfo
findPathsForApp(appState: appState, appInfo: appState.appInfo)
withAnimation(Animation.easeIn(duration: 0.4)) {
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")
Spacer()
}
.frame(width: 300)
.frame(width: 250)
.padding(.vertical)
} else {
VStack(alignment: .center) {
VStack(alignment: .center, spacing: 20) {
HStack {
SearchBar(search: $search)
SearchBar(search: $search, reload: $reload)
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
@@ -89,7 +89,7 @@ struct AppListView: View {
Divider().padding(.horizontal, 5)
}
}
.padding(.bottom)
// .padding(.bottom)
}
}
@@ -113,7 +113,7 @@ struct AppListView: View {
.scrollIndicators(.never)
}
.frame(width: 300)
.frame(width: 250)
.padding(.vertical)
}
@@ -200,11 +200,12 @@ struct AppDetailsEmptyView: View {
struct SearchBar: View {
@Binding var search: String
@Binding var reload: Bool
var body: some View {
HStack {
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)
}
@@ -220,11 +221,13 @@ struct Header: View {
Text(title).opacity(0.5)
Spacer()
Text("\(count)")
.frame(minWidth: 30, minHeight: 15)
.font(.system(size: 10))
.frame(minWidth: count > 99 ? 30 : 20, minHeight: 15)
.padding(2)
.background(Color("mode").opacity(0.1))
.clipShape(.capsule)
// .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
private var types: [UTType] = [.fileURL]
@Environment(\.colorScheme) var colorScheme
@State private var phase: CGFloat = 0
@State private var phase: CGFloat = 4
public init(appState: AppState) {
self.appState = appState
@@ -39,7 +39,9 @@ struct DropTarget: View, DropDelegate {
Image(mini ? "pear" : "logo")
.resizable()
.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)
.background {
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 {
withAnimation(.linear.speed(0.5).repeat(while: ants, autoreverses: false)) {
if ants {
phase -= 20
phase -= 22
}
}
}
.onChange(of: ants) { newValue in
withAnimation(.linear.speed(0.5).repeat(while: newValue, autoreverses: false)) {
phase -= 20
phase -= 22
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
+19 -156
View File
@@ -25,6 +25,7 @@ struct FilesView: View {
ProgressView("Finding application files..")
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(.opacity)
} else {
VStack() {
@@ -45,19 +46,19 @@ struct FilesView: View {
HStack(alignment: .center) {
VStack(alignment: .leading, spacing: 5){
HStack {
Text("\(appState.appInfo.appName)").font(mini ? .title2 :.title).fontWeight(.bold)
Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold)
// .foregroundStyle(Color("AccentColor"))
Text("").foregroundStyle(Color("AccentColor"))
Text("\(appState.appInfo.appVersion)").font(.title3)
// .foregroundStyle(.gray.opacity(0.8))
}
Text("\(appState.appInfo.bundleIdentifier)").font(mini ? .footnote :.title3)
Text("\(appState.appInfo.bundleIdentifier)").font(.title3)
.foregroundStyle((.gray.opacity(0.8)))
}
Spacer()
Text("\(appSize)").font(mini ? .title2 :.title).fontWeight(.bold)
Text("\(appSize)").font(.title).fontWeight(.bold)
// .foregroundStyle(Color("AccentColor"))
}
@@ -74,6 +75,19 @@ struct FilesView: View {
// .foregroundStyle(Color("AccentColor").opacity(0.7))
.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 {
HStack(alignment: .center) {
Button(""){
@@ -103,7 +117,7 @@ struct FilesView: View {
}
.padding(20)
}
.padding([.horizontal, .bottom])
// .padding(.horizontal)
.background(
RoundedRectangle(cornerRadius: 8)
// .strokeBorder(Color("AccentColor"), lineWidth: 0.5)
@@ -142,16 +156,6 @@ struct FilesView: View {
HStack() {
Spacer()
// if appState.trashLaunch {
// Button("Close") {
// Task {
// NSApp.terminate(nil)
// exit(0)
// }
// }
// .buttonStyle(WindowActionButton(action: .cancel))
// }
Button("Uninstall") {
Task {
@@ -182,7 +186,7 @@ struct FilesView: View {
}
}
// .frame(minWidth: 700)
// .frame(minWidth: 500, minHeight: 500)
.onAppear {
Task {
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
// }
//}
+27 -15
View File
@@ -15,6 +15,7 @@ struct MiniMode: View {
@State private var showSys: Bool = true
@State private var showUsr: Bool = true
@State private var reload: Bool = false
@AppStorage("settings.general.glass") private var glass: Bool = false
@@ -26,7 +27,7 @@ struct MiniMode: View {
VStack(spacing: 0) {
if appState.currentView == .empty {
TopBarMini(reload: $reload, search: $search)
AppDetailsEmptyView()
MiniEmptyView()
} else if appState.currentView == .files {
TopBarMini(reload: $reload, search: $search)
FilesView()
@@ -39,8 +40,11 @@ struct MiniMode: View {
// .padding(.leading, appState.sidebar ? 0 : 10)
.transition(.move(edge: .leading))
}
.frame(minWidth: 300, minHeight: 300)
.edgesIgnoringSafeArea(.all)
.background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil)
.onOpenURL(perform: { url in
let deeplinkManager = DeeplinkManager()
deeplinkManager.manage(url: url, appState: appState)
@@ -70,6 +74,11 @@ struct MiniEmptyView: View {
DropTarget(appState: appState)
.frame(maxWidth: mini ? 300 : 500)
Text("Drop an app to begin")
.font(.title3)
.padding(.bottom, 25)
.opacity(0.5)
Spacer()
if appState.isReminderVisible {
@@ -87,11 +96,9 @@ struct MiniEmptyView: View {
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 {
VStack(alignment: .center) {
// VStack(alignment: .center, spacing: 20) {
// HStack {
// SearchBar(search: $search)
// }
// }
// .padding(.horizontal, 5)
// .padding(.bottom)
ScrollView {
VStack(alignment: .leading) {
@@ -161,7 +160,6 @@ struct MiniAppView: View {
Divider().padding(.horizontal, 5)
}
}
.padding(.bottom)
}
}
@@ -185,13 +183,27 @@ struct MiniAppView: View {
.scrollIndicators(.never)
}
.padding(.vertical)
// .padding(.vertical)
}
}
}
.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)
}
}
}
+35 -44
View File
@@ -18,9 +18,38 @@ struct TopBarMini: View {
var body: some View {
HStack(alignment: .center, spacing: 5) {
Spacer()
if appState.currentView == .apps {
// HStack {
// Spacer()
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 {
HStack(alignment: .center, spacing: 0) {
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
appState.currentView = .empty
@@ -28,10 +57,6 @@ struct TopBarMini: View {
}
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode")))
}
} else if appState.currentView == .empty {
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
@@ -55,56 +80,22 @@ struct TopBarMini: View {
.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)
.padding(.top, 30)
.padding(.horizontal, 10)
.padding(.top, 10)
.padding(.bottom, 15)
}
}
struct SearchBarMini: View {
@Binding var search: String
@Binding var reload: Bool
var body: some View {
HStack {
TextField("Search", text: $search)
.textFieldStyle(SimpleSearchStyle(trash: true, text: $search))
.textFieldStyle(SimpleSearchStyle(trash: true, reload: $reload, text: $search))
}
.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))
}
}