mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 07:19:04 +00:00
v2.6
This commit is contained in:
@@ -543,7 +543,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 = 16;
|
CURRENT_PROJECT_VERSION = 17;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BK8443AXLU;
|
DEVELOPMENT_TEAM = BK8443AXLU;
|
||||||
@@ -561,7 +561,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 2.5;
|
MARKETING_VERSION = 2.6;
|
||||||
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;
|
||||||
@@ -578,7 +578,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 = 16;
|
CURRENT_PROJECT_VERSION = 17;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = BK8443AXLU;
|
DEVELOPMENT_TEAM = BK8443AXLU;
|
||||||
@@ -596,7 +596,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 2.5;
|
MARKETING_VERSION = 2.6;
|
||||||
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;
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import SwiftUI
|
|||||||
struct AppCommands: Commands {
|
struct AppCommands: Commands {
|
||||||
|
|
||||||
let appState: AppState
|
let appState: AppState
|
||||||
|
let locations: Locations
|
||||||
|
|
||||||
init(appState: AppState) {
|
init(appState: AppState, locations: Locations) {
|
||||||
self.appState = appState
|
self.appState = appState
|
||||||
|
self.locations = locations
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some Commands {
|
var body: some Commands {
|
||||||
@@ -44,7 +46,7 @@ struct AppCommands: Commands {
|
|||||||
.keyboardShortcut("r", modifiers: .command)
|
.keyboardShortcut("r", modifiers: .command)
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
uninstallPearcleaner(appState: appState)
|
uninstallPearcleaner(appState: appState, locations: locations)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Uninstall Pearcleaner")
|
Text("Uninstall Pearcleaner")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,20 +12,18 @@ let home = FileManager.default.homeDirectoryForCurrentUser.path
|
|||||||
|
|
||||||
class AppState: ObservableObject
|
class AppState: ObservableObject
|
||||||
{
|
{
|
||||||
// @Published var showFiles: Bool = false
|
|
||||||
@Published var appInfo: AppInfo
|
@Published var appInfo: AppInfo
|
||||||
@Published var paths: [URL] = []
|
@Published var paths: [URL] = []
|
||||||
@Published var sortedApps: (userApps: [AppInfo], systemApps: [AppInfo]) = ([], [])
|
@Published var sortedApps: (userApps: [AppInfo], systemApps: [AppInfo]) = ([], [])
|
||||||
@Published var selectedItems = Set<URL>()
|
@Published var selectedItems = Set<URL>()
|
||||||
@Published var alertType = AlertType.off
|
@Published var alertType = AlertType.off
|
||||||
@Published var currentView = CurrentDetailsView.empty
|
@Published var currentView = CurrentDetailsView.empty
|
||||||
// @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)
|
@Published var progressBar: (String, Double) = ("Ready", 0.0)
|
||||||
|
// @Published var progressManager = ProgressManager()
|
||||||
@Published var reload: Bool = false
|
@Published var reload: Bool = false
|
||||||
|
|
||||||
|
|
||||||
@@ -46,6 +44,39 @@ class AppState: ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressManager: ObservableObject {
|
||||||
|
@Published var progress: Double = 0.0
|
||||||
|
@Published var total: Double = 0.0
|
||||||
|
@Published var status: String = "Ready"
|
||||||
|
|
||||||
|
func setTotal(_ total: Double) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.total = total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateProgress() {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.progress = min(max(0.0, self.progress + 1.0), Double(self.total))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStatus(status: String) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.status = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetProgress() {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.progress = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct AppInfo: Identifiable, Hashable {
|
struct AppInfo: Identifiable, Hashable {
|
||||||
let id: UUID
|
let id: UUID
|
||||||
let path: URL
|
let path: URL
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ class DeeplinkManager {
|
|||||||
static let query = "path"
|
static let query = "path"
|
||||||
}
|
}
|
||||||
|
|
||||||
func manage(url: URL, appState: AppState) {
|
func manage(url: URL, appState: AppState, locations: Locations) {
|
||||||
if url.pathExtension == "app" {
|
if url.pathExtension == "app" {
|
||||||
handleAppBundle(url: url, appState: appState)
|
handleAppBundle(url: url, appState: appState, locations: locations)
|
||||||
} else if url.scheme == DeepLinkConstants.scheme,
|
} else if url.scheme == DeepLinkConstants.scheme,
|
||||||
url.host == DeepLinkConstants.host,
|
url.host == DeepLinkConstants.host,
|
||||||
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||||
@@ -34,7 +34,7 @@ class DeeplinkManager {
|
|||||||
let appInfo = getAppInfo(atPath: pathURL)
|
let appInfo = getAppInfo(atPath: pathURL)
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
appState.appInfo = appInfo!
|
appState.appInfo = appInfo!
|
||||||
findPathsForApp(appState: appState, appInfo: appState.appInfo)
|
findPathsForApp(appState: appState, locations: locations)
|
||||||
if self.mini {
|
if self.mini {
|
||||||
self.showPopover = true
|
self.showPopover = true
|
||||||
} else {
|
} else {
|
||||||
@@ -72,11 +72,11 @@ class DeeplinkManager {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func handleAppBundle(url: URL, appState: AppState) {
|
func handleAppBundle(url: URL, appState: AppState, locations: Locations) {
|
||||||
let appInfo = getAppInfo(atPath: url)
|
let appInfo = getAppInfo(atPath: url)
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
appState.appInfo = appInfo!
|
appState.appInfo = appInfo!
|
||||||
findPathsForApp(appState: appState, appInfo: appState.appInfo)
|
findPathsForApp(appState: appState, locations: locations)
|
||||||
if self.mini {
|
if self.mini {
|
||||||
self.showPopover = true
|
self.showPopover = true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
struct Locations {
|
class Locations: ObservableObject {
|
||||||
struct Category {
|
struct Category {
|
||||||
let name: String
|
let name: String
|
||||||
var paths: [String]
|
var paths: [String]
|
||||||
@@ -19,8 +19,8 @@ struct Locations {
|
|||||||
let tempDir: String
|
let tempDir: String
|
||||||
|
|
||||||
var apps: Category
|
var apps: Category
|
||||||
var widgets: Category
|
// var widgets: Category
|
||||||
var plugins: Category
|
// var plugins: Category
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.home = FileManager.default.homeDirectoryForCurrentUser.path
|
self.home = FileManager.default.homeDirectoryForCurrentUser.path
|
||||||
@@ -32,7 +32,7 @@ struct Locations {
|
|||||||
"\(home)/Library",
|
"\(home)/Library",
|
||||||
"\(home)/Library/Application Scripts",
|
"\(home)/Library/Application Scripts",
|
||||||
"\(home)/Library/Application Support",
|
"\(home)/Library/Application Support",
|
||||||
"\(home)/Library/Application Support/CrashReporter",
|
// "\(home)/Library/Application Support/CrashReporter",
|
||||||
"\(home)/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments",
|
"\(home)/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments",
|
||||||
"\(home)/Library/Containers",
|
"\(home)/Library/Containers",
|
||||||
// "\(home)/Library/Group Containers", // This is now handled by the function getGroupContainers()
|
// "\(home)/Library/Group Containers", // This is now handled by the function getGroupContainers()
|
||||||
@@ -71,34 +71,44 @@ struct Locations {
|
|||||||
tempDir
|
tempDir
|
||||||
])
|
])
|
||||||
|
|
||||||
self.widgets = Category(name: "Widgets", paths: [
|
// Append Application Support subfolders for deeper search
|
||||||
// User
|
do {
|
||||||
"~/Library/Widgets",
|
let subfolders = try appSupSubfolders()
|
||||||
// System
|
for folder in subfolders {
|
||||||
"/Library/Widgets"
|
self.apps.paths.append("\(home)/Library/Application Support/\(folder)")
|
||||||
])
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error getting subfolders: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
self.plugins = Category(name: "Plugins", paths: [
|
// self.widgets = Category(name: "Widgets", paths: [
|
||||||
// User
|
// // User
|
||||||
"~/Library/Contextual Menu Items",
|
// "~/Library/Widgets",
|
||||||
"~/Library/InputManagers",
|
// // System
|
||||||
"~/Library/Internet Plug-Ins",
|
// "/Library/Widgets"
|
||||||
"~/Library/Mail/Bundles",
|
// ])
|
||||||
"~/Library/PreferencePanes",
|
//
|
||||||
"~/Library/QuickLook",
|
// self.plugins = Category(name: "Plugins", paths: [
|
||||||
"~/Library/QuickTime",
|
// // User
|
||||||
"~/Library/Screen Savers",
|
// "~/Library/Contextual Menu Items",
|
||||||
"~/Library/Spotlight",
|
// "~/Library/InputManagers",
|
||||||
// System
|
// "~/Library/Internet Plug-Ins",
|
||||||
"/Library/Contextual Menu Items",
|
// "~/Library/Mail/Bundles",
|
||||||
"/Library/InputManagers",
|
// "~/Library/PreferencePanes",
|
||||||
"/Library/Internet Plug-Ins",
|
// "~/Library/QuickLook",
|
||||||
"/Library/Mail/Bundles",
|
// "~/Library/QuickTime",
|
||||||
"/Library/PreferencePanes",
|
// "~/Library/Screen Savers",
|
||||||
"/Library/QuickLook",
|
// "~/Library/Spotlight",
|
||||||
"/Library/QuickTime",
|
// // System
|
||||||
"/Library/Screen Savers",
|
// "/Library/Contextual Menu Items",
|
||||||
"/Library/Spotlight",
|
// "/Library/InputManagers",
|
||||||
])
|
// "/Library/Internet Plug-Ins",
|
||||||
|
// "/Library/Mail/Bundles",
|
||||||
|
// "/Library/PreferencePanes",
|
||||||
|
// "/Library/QuickLook",
|
||||||
|
// "/Library/QuickTime",
|
||||||
|
// "/Library/Screen Savers",
|
||||||
|
// "/Library/Spotlight",
|
||||||
|
// ])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,6 +268,24 @@ func darwinCT() -> (String, String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add subfolders of ~/Library/Application Support/ to locations for deeper search
|
||||||
|
func appSupSubfolders() throws -> [String] {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
let appSup = "\(home)/Library/Application Support/"
|
||||||
|
let subfolders = try fileManager.contentsOfDirectory(atPath: appSup)
|
||||||
|
let exclusionRegex = try NSRegularExpression(pattern: "\\bcom\\.apple\\b", options: [])
|
||||||
|
let exclusions = ["MobileSync", ".DS_Store", "Xcode", "SyncServices", "networkserviceproxy", "DiskImages", "CallHistoryTransactions", "App Store", "CloudDocs", "icdd", "iCloud", "Instruments", "AddressBook", "FaceTime", "AskPermission", "CallHistoryDB"]
|
||||||
|
|
||||||
|
let allowedFolders = subfolders.filter { folder in
|
||||||
|
let range = NSRange(location: 0, length: folder.utf16.count)
|
||||||
|
return exclusionRegex.firstMatch(in: folder, options: [], range: range) == nil && !exclusions.contains(folder)
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowedFolders
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Check if app is running before deleting app files
|
// Check if app is running before deleting app files
|
||||||
func killApp(appId: String, completion: @escaping () -> Void = {}) {
|
func killApp(appId: String, completion: @escaping () -> Void = {}) {
|
||||||
@@ -284,17 +302,19 @@ func killApp(appId: String, completion: @escaping () -> Void = {}) {
|
|||||||
|
|
||||||
|
|
||||||
// Find all possible paths for an app based on name/bundle id
|
// Find all possible paths for an app based on name/bundle id
|
||||||
func findPathsForApp(appState: AppState, appInfo: AppInfo) {
|
func findPathsForApp(appState: AppState, locations: Locations) {
|
||||||
Task(priority: .high) {
|
Task(priority: .high) {
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
appState.paths = []
|
appState.paths = []
|
||||||
}
|
}
|
||||||
|
let appInfo = appState.appInfo
|
||||||
var collection: [URL] = []
|
var collection: [URL] = []
|
||||||
if let url = URL(string: appInfo.path.absoluteString) {
|
if let url = URL(string: appInfo.path.absoluteString) {
|
||||||
collection.insert(url, at: 0)
|
collection.insert(url, at: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
|
// let progressManager = appState.progressManager
|
||||||
let dispatchGroup = DispatchGroup()
|
let dispatchGroup = DispatchGroup()
|
||||||
var bundleComponents = appInfo.bundleIdentifier.components(separatedBy: ".")
|
var bundleComponents = appInfo.bundleIdentifier.components(separatedBy: ".")
|
||||||
if let lastComponent = bundleComponents.last, let rangeOfDash = lastComponent.range(of: "-") {
|
if let lastComponent = bundleComponents.last, let rangeOfDash = lastComponent.range(of: "-") {
|
||||||
@@ -310,27 +330,37 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) {
|
|||||||
let nameL = appInfo.appName.pearFormat()
|
let nameL = appInfo.appName.pearFormat()
|
||||||
let nameP = appInfo.path.lastPathComponent.replacingOccurrences(of: ".app", with: "")
|
let nameP = appInfo.path.lastPathComponent.replacingOccurrences(of: ".app", with: "")
|
||||||
let bundleIdentifierL = appInfo.bundleIdentifier.pearFormat()
|
let bundleIdentifierL = appInfo.bundleIdentifier.pearFormat()
|
||||||
let locations = Locations()
|
|
||||||
|
|
||||||
for location in locations.apps.paths {
|
for location in locations.apps.paths {
|
||||||
if !fileManager.fileExists(atPath: location) {
|
if !fileManager.fileExists(atPath: location) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
// withAnimation {
|
||||||
|
// progressManager.updateStatus(status: location)
|
||||||
|
// progressManager.updateProgress()
|
||||||
|
// progressManager.objectWillChange.send()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
dispatchGroup.enter() // Enter the dispatch group
|
dispatchGroup.enter() // Enter the dispatch group
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
let contents = try fileManager.contentsOfDirectory(atPath: location)
|
let contents = try fileManager.contentsOfDirectory(atPath: location)
|
||||||
|
|
||||||
for item in contents {
|
for item in contents {
|
||||||
|
|
||||||
let itemURL = URL(fileURLWithPath: location).appendingPathComponent(item)
|
let itemURL = URL(fileURLWithPath: location).appendingPathComponent(item)
|
||||||
let itemL = ("\(item)").replacingOccurrences(of: ".", with: "").replacingOccurrences(of: " ", with: "").lowercased()
|
let itemL = ("\(item)").replacingOccurrences(of: ".", with: "").replacingOccurrences(of: " ", with: "").lowercased()
|
||||||
|
|
||||||
|
|
||||||
if collection.contains(itemURL) {
|
if collection.contains(itemURL) {
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
// Catch web app plist files
|
// Catch web app plist files
|
||||||
if appInfo.webApp {
|
if appInfo.webApp {
|
||||||
@@ -348,7 +378,7 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Catch Xcode files
|
// Catch Xcode files
|
||||||
else if itemL.contains("xcode") {
|
else if itemL.contains("xcode") {
|
||||||
if itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 4 && itemL.contains(nameL)) {
|
if itemL.contains(bundle) || itemL.contains(bundleIdentifierL) || (nameL.count > 4 && itemL.contains(nameL)) {
|
||||||
collection.append(itemURL)
|
collection.append(itemURL)
|
||||||
}
|
}
|
||||||
@@ -361,9 +391,12 @@ func findPathsForApp(appState: AppState, appInfo: AppInfo) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
print("Error processing location:", location, error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try await Task.sleep(nanoseconds: 50_000_000)
|
||||||
|
|
||||||
dispatchGroup.leave() // Leave the dispatch group
|
dispatchGroup.leave() // Leave the dispatch group
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,16 +396,17 @@ func presentAlert(appState: AppState) -> Alert {
|
|||||||
|
|
||||||
|
|
||||||
// --- Pearcleaner Uninstall --
|
// --- Pearcleaner Uninstall --
|
||||||
func uninstallPearcleaner(appState: AppState) {
|
func uninstallPearcleaner(appState: AppState, locations: Locations) {
|
||||||
|
|
||||||
// Unload Sentinel Monitor if running
|
// Unload Sentinel Monitor if running
|
||||||
launchctl(load: false)
|
launchctl(load: false)
|
||||||
|
|
||||||
// Get app info for Pearcleaner
|
// Get app info for Pearcleaner
|
||||||
let appInfo = getAppInfo(atPath: Bundle.main.bundleURL)
|
let appInfo = getAppInfo(atPath: Bundle.main.bundleURL)
|
||||||
|
appState.appInfo = appInfo!
|
||||||
|
|
||||||
// Find application files for Pearcleaner
|
// Find application files for Pearcleaner
|
||||||
findPathsForApp(appState: appState, appInfo: appInfo!)
|
findPathsForApp(appState: appState, locations: locations)
|
||||||
|
|
||||||
// Kill Pearcleaner and tell Finder to trash the files
|
// Kill Pearcleaner and tell Finder to trash the files
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import AppKit
|
|||||||
struct PearcleanerApp: App {
|
struct PearcleanerApp: App {
|
||||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||||
@StateObject var appState = AppState()
|
@StateObject var appState = AppState()
|
||||||
|
@StateObject var locations = Locations()
|
||||||
@State private var windowSettings = WindowSettings()
|
@State private var windowSettings = WindowSettings()
|
||||||
@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
|
||||||
@@ -31,8 +32,10 @@ struct PearcleanerApp: App {
|
|||||||
|
|
||||||
if !mini {
|
if !mini {
|
||||||
AppListView(search: $search, showPopover: $showPopover)
|
AppListView(search: $search, showPopover: $showPopover)
|
||||||
|
.environmentObject(locations)
|
||||||
} else {
|
} else {
|
||||||
MiniMode(search: $search, showPopover: $showPopover)
|
MiniMode(search: $search, showPopover: $showPopover)
|
||||||
|
.environmentObject(locations)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -42,14 +45,14 @@ struct PearcleanerApp: App {
|
|||||||
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
|
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
|
||||||
.onOpenURL(perform: { url in
|
.onOpenURL(perform: { url in
|
||||||
let deeplinkManager = DeeplinkManager(showPopover: $showPopover)
|
let deeplinkManager = DeeplinkManager(showPopover: $showPopover)
|
||||||
deeplinkManager.manage(url: url, appState: appState)
|
deeplinkManager.manage(url: url, appState: appState, locations: locations)
|
||||||
})
|
})
|
||||||
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers, _ in
|
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers, _ in
|
||||||
for provider in providers {
|
for provider in providers {
|
||||||
provider.loadItem(forTypeIdentifier: "public.file-url") { data, error in
|
provider.loadItem(forTypeIdentifier: "public.file-url") { data, error in
|
||||||
if let data = data as? Data, let url = URL(dataRepresentation: data, relativeTo: nil) {
|
if let data = data as? Data, let url = URL(dataRepresentation: data, relativeTo: nil) {
|
||||||
let deeplinkManager = DeeplinkManager(showPopover: $showPopover)
|
let deeplinkManager = DeeplinkManager(showPopover: $showPopover)
|
||||||
deeplinkManager.manage(url: url, appState: appState)
|
deeplinkManager.manage(url: url, appState: appState, locations: locations)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,6 +77,9 @@ struct PearcleanerApp: App {
|
|||||||
appState.sortedApps.userApps = sortedApps.userApps
|
appState.sortedApps.userApps = sortedApps.userApps
|
||||||
appState.sortedApps.systemApps = sortedApps.systemApps
|
appState.sortedApps.systemApps = sortedApps.systemApps
|
||||||
|
|
||||||
|
// Load progressbar total
|
||||||
|
// appState.progressManager.total = Double(locations.apps.paths.count)
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@@ -112,7 +118,7 @@ struct PearcleanerApp: App {
|
|||||||
.windowStyle(.hiddenTitleBar)
|
.windowStyle(.hiddenTitleBar)
|
||||||
.windowResizability(.contentMinSize)
|
.windowResizability(.contentMinSize)
|
||||||
.commands {
|
.commands {
|
||||||
AppCommands(appState: appState)
|
AppCommands(appState: appState, locations: locations)
|
||||||
CommandGroup(replacing: .newItem, addition: { })
|
CommandGroup(replacing: .newItem, addition: { })
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ struct AppListItems: View {
|
|||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@State private var isHovered = false
|
@State private var isHovered = false
|
||||||
|
@State private var windowSettings = WindowSettings()
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
|
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
|
@EnvironmentObject var locations: Locations
|
||||||
|
let itemId = UUID()
|
||||||
let appInfo: AppInfo
|
let appInfo: AppInfo
|
||||||
var isSelected: Bool {
|
var isSelected: Bool {
|
||||||
appState.appInfo == appInfo
|
appState.appInfo == appInfo
|
||||||
@@ -74,20 +77,6 @@ struct AppListItems: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// .background(
|
|
||||||
// RoundedRectangle(cornerRadius: 6)
|
|
||||||
// .fill(isSelected ? Color("AccentColor").opacity(0.15) : (isHovered ? Color("AccentColor").opacity(0.1) : Color.white.opacity(colorScheme == .dark ? 0.05 : 0.8)))
|
|
||||||
// .shadow(color: .black.opacity(0.2), radius: 2, x: 0, y: 1)
|
|
||||||
// .overlay(
|
|
||||||
// RoundedRectangle(cornerRadius: 6)
|
|
||||||
// .strokeBorder(Color("AccentColor").opacity(isHovered ? 0.7 : 0.1), lineWidth: 1)
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
|
|
||||||
// Rectangle()
|
|
||||||
// .frame(maxWidth: isSelected ? .infinity : (isHovered ? .infinity : 30), maxHeight: 2)
|
|
||||||
// .foregroundStyle(Color("AccentColor"))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 5)
|
.padding(.horizontal, 5)
|
||||||
.help(appInfo.appName)
|
.help(appInfo.appName)
|
||||||
@@ -97,18 +86,43 @@ struct AppListItems: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
updateOnMain {
|
showPopover = false
|
||||||
appState.appInfo = .empty
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||||
appState.appInfo = appInfo
|
updateOnMain {
|
||||||
findPathsForApp(appState: appState, appInfo: appState.appInfo)
|
appState.appInfo = .empty
|
||||||
withAnimation(Animation.easeIn(duration: 0.4)) {
|
appState.appInfo = appInfo
|
||||||
if mini {
|
// appState.progressManager.resetProgress()
|
||||||
showPopover.toggle()
|
findPathsForApp(appState: appState, locations: locations)
|
||||||
} else {
|
withAnimation(Animation.easeIn(duration: 0.4)) {
|
||||||
appState.currentView = .files
|
if mini {
|
||||||
|
showPopover.toggle()
|
||||||
|
} else {
|
||||||
|
appState.currentView = .files
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.popover(isPresented: Binding(
|
||||||
|
get: { showPopover && appState.appInfo.id == appInfo.id},
|
||||||
|
set: { _ in showPopover = false}
|
||||||
|
), attachmentAnchor: .rect(.rect(CGRect(x: windowSettings.loadWindowSettings().width - 17, y: 15, width: 0, height: 0))), arrowEdge: .trailing) {
|
||||||
|
|
||||||
|
// .popover(isPresented: $showPopover, arrowEdge: .trailing) {
|
||||||
|
VStack {
|
||||||
|
FilesView(showPopover: $showPopover, search: $search)
|
||||||
|
.id(appState.appInfo.id)
|
||||||
|
}
|
||||||
|
.interactiveDismissDisabled(popoverStay)
|
||||||
|
.background(
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color("pop"))
|
||||||
|
.padding(-80)
|
||||||
|
)
|
||||||
|
.frame(minWidth: 650, minHeight: 500)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ struct AppListView: View {
|
|||||||
struct AppDetailsEmptyView: View {
|
struct AppDetailsEmptyView: View {
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
|
@EnvironmentObject var locations: Locations
|
||||||
@State private var animateGradient: Bool = false
|
@State private var animateGradient: Bool = false
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
|
|
||||||
@@ -191,7 +192,7 @@ struct AppDetailsEmptyView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
DropTarget(appState: appState, showPopover: $showPopover)
|
DropTarget(appState: appState, locations: locations, showPopover: $showPopover)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,16 @@ struct DropTarget: View, DropDelegate {
|
|||||||
@AppStorage("settings.general.ants") private var ants: Bool = false
|
@AppStorage("settings.general.ants") private var ants: Bool = false
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
@ObservedObject var appState: AppState
|
@ObservedObject var appState: AppState
|
||||||
|
@ObservedObject var locations: Locations
|
||||||
@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 = 4
|
@State private var phase: CGFloat = 4
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
|
|
||||||
public init(appState: AppState, showPopover: Binding<Bool>) {
|
public init(appState: AppState, locations: Locations, showPopover: Binding<Bool>) {
|
||||||
self.appState = appState
|
self.appState = appState
|
||||||
|
self.locations = locations
|
||||||
_showPopover = showPopover
|
_showPopover = showPopover
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +134,7 @@ struct DropTarget: View, DropDelegate {
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.ants = false
|
self.ants = false
|
||||||
appState.appInfo = appInfo!
|
appState.appInfo = appInfo!
|
||||||
findPathsForApp(appState: appState, appInfo: appState.appInfo)
|
findPathsForApp(appState: appState, locations: locations)
|
||||||
if mini {
|
if mini {
|
||||||
showPopover = true
|
showPopover = true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct FilesView: View {
|
struct FilesView: View {
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
|
@EnvironmentObject var locations: Locations
|
||||||
@State private var appSize: String = ""
|
@State private var appSize: String = ""
|
||||||
@State private var showDetails: Bool = false
|
@State private var showDetails: Bool = false
|
||||||
@State private var showPop: Bool = false
|
@State private var showPop: Bool = false
|
||||||
@@ -25,7 +26,28 @@ struct FilesView: View {
|
|||||||
if !self.showDetails {
|
if !self.showDetails {
|
||||||
VStack {
|
VStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
ProgressView("Finding application files..")
|
// ProgressView("Finding application files..")
|
||||||
|
// .progressViewStyle(.linear)
|
||||||
|
// Spacer()
|
||||||
|
Text("Finding application files..").font(.title3)
|
||||||
|
.foregroundStyle((.gray.opacity(0.8)))
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(.linear)
|
||||||
|
.frame(width: 400, height: 10)
|
||||||
|
// ProgressView("\(appState.progressManager.status)", value: appState.progressManager.progress, total: Double(appState.progressManager.total))
|
||||||
|
// .progressViewStyle(LinearProgressViewStyle(tint: .blue))
|
||||||
|
// .frame(width: 400, height: 10)
|
||||||
|
// .padding(.top)
|
||||||
|
// .onReceive(appState.progressManager.$progress) { newProgress in
|
||||||
|
// DispatchQueue.main.async {
|
||||||
|
// appState.objectWillChange.send()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .onReceive(appState.progressManager.$status) { newStatus in
|
||||||
|
// DispatchQueue.main.async {
|
||||||
|
// appState.objectWillChange.send()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
@@ -134,7 +156,7 @@ struct FilesView: View {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
ScrollView {
|
ScrollView() {
|
||||||
VStack {
|
VStack {
|
||||||
ForEach(Array(zip(appState.paths, itemDetails)), id: \.0) { path, details in
|
ForEach(Array(zip(appState.paths, itemDetails)), id: \.0) { path, details in
|
||||||
if let firstPath = appState.paths.first, path == firstPath {
|
if let firstPath = appState.paths.first, path == firstPath {
|
||||||
@@ -250,10 +272,10 @@ struct FilesView: View {
|
|||||||
}
|
}
|
||||||
if let appSize = totalSizeOnDisk(for: appState.paths) {
|
if let appSize = totalSizeOnDisk(for: appState.paths) {
|
||||||
self.appSize = "\(appSize)"
|
self.appSize = "\(appSize)"
|
||||||
self.showDetails = true
|
|
||||||
} else {
|
} else {
|
||||||
print("Error calculating the total size on disk.")
|
print("Error calculating the total size on disk.")
|
||||||
}
|
}
|
||||||
|
self.showDetails = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
@@ -326,7 +348,7 @@ struct FileDetailsItem: View {
|
|||||||
.help(path.lastPathComponent)
|
.help(path.lastPathComponent)
|
||||||
Text(path.path)
|
Text(path.path)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.lineLimit(1)
|
.lineLimit(2)
|
||||||
.truncationMode(.tail)
|
.truncationMode(.tail)
|
||||||
.opacity(0.5)
|
.opacity(0.5)
|
||||||
// .foregroundStyle(Color("AccentColor"))
|
// .foregroundStyle(Color("AccentColor"))
|
||||||
|
|||||||
@@ -39,20 +39,20 @@ struct MiniMode: View {
|
|||||||
}
|
}
|
||||||
// .padding(.leading, appState.sidebar ? 0 : 10)
|
// .padding(.leading, appState.sidebar ? 0 : 10)
|
||||||
.transition(.move(edge: .leading))
|
.transition(.move(edge: .leading))
|
||||||
.popover(isPresented: $showPopover, arrowEdge: .trailing) {
|
// .popover(isPresented: $showPopover, arrowEdge: .trailing) {
|
||||||
VStack {
|
// VStack {
|
||||||
FilesView(showPopover: $showPopover, search: $search)
|
// FilesView(showPopover: $showPopover, search: $search)
|
||||||
.id(appState.appInfo.id)
|
// .id(appState.appInfo.id)
|
||||||
}
|
// }
|
||||||
.interactiveDismissDisabled(popoverStay)
|
// .interactiveDismissDisabled(popoverStay)
|
||||||
.background(
|
// .background(
|
||||||
Rectangle()
|
// Rectangle()
|
||||||
.fill(Color("pop"))
|
// .fill(Color("pop"))
|
||||||
.padding(-80)
|
// .padding(-80)
|
||||||
)
|
// )
|
||||||
.frame(minWidth: 600, minHeight: 500)
|
// .frame(minWidth: 600, minHeight: 500)
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -97,6 +97,7 @@ struct MiniMode: View {
|
|||||||
struct MiniEmptyView: View {
|
struct MiniEmptyView: View {
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
|
@EnvironmentObject var locations: Locations
|
||||||
@State private var animateGradient: Bool = false
|
@State private var animateGradient: Bool = false
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@@ -106,7 +107,7 @@ struct MiniEmptyView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
DropTarget(appState: appState, showPopover: $showPopover)
|
DropTarget(appState: appState, locations: locations, showPopover: $showPopover)
|
||||||
.frame(maxWidth: mini ? 300 : 500)
|
.frame(maxWidth: mini ? 300 : 500)
|
||||||
|
|
||||||
Text("Drop an app to begin")
|
Text("Drop an app to begin")
|
||||||
|
|||||||
Reference in New Issue
Block a user