mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 06:09:01 +00:00
Updates
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71848432B8D2D600046CB13 /* ZombieView.swift */; };
|
C71848442B8D2D600046CB13 /* ZombieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C71848432B8D2D600046CB13 /* ZombieView.swift */; };
|
||||||
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; };
|
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; };
|
||||||
C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; };
|
C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; };
|
||||||
|
C72F4B6C2C5DF4ED00B04F60 /* SunburstChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72F4B6B2C5DF4ED00B04F60 /* SunburstChart.swift */; };
|
||||||
C736E7652C2DF1C9009EDB6A /* AppPathsFetch-Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */; };
|
C736E7652C2DF1C9009EDB6A /* AppPathsFetch-Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */; };
|
||||||
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */; };
|
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */; };
|
||||||
C74D76B72C5C4ABC00748DD1 /* TreeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */; };
|
C74D76B72C5C4ABC00748DD1 /* TreeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */; };
|
||||||
@@ -102,6 +103,7 @@
|
|||||||
C72893042AFD42E600C8C1CD /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
|
C72893042AFD42E600C8C1CD /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
|
||||||
C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PearcleanerSentinel; sourceTree = BUILT_PRODUCTS_DIR; };
|
C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PearcleanerSentinel; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
C72893112AFD51EA00C8C1CD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
C72893112AFD51EA00C8C1CD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||||
|
C72F4B6B2C5DF4ED00B04F60 /* SunburstChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SunburstChart.swift; sourceTree = "<group>"; };
|
||||||
C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPathsFetch-Async.swift"; sourceTree = "<group>"; };
|
C736E7642C2DF1C9009EDB6A /* AppPathsFetch-Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppPathsFetch-Async.swift"; sourceTree = "<group>"; };
|
||||||
C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathsFetch.swift; sourceTree = "<group>"; };
|
C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathsFetch.swift; sourceTree = "<group>"; };
|
||||||
C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeMap.swift; sourceTree = "<group>"; };
|
C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeMap.swift; sourceTree = "<group>"; };
|
||||||
@@ -220,6 +222,7 @@
|
|||||||
C74D76B92C5D2DF300748DD1 /* FileSearch.swift */,
|
C74D76B92C5D2DF300748DD1 /* FileSearch.swift */,
|
||||||
C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */,
|
C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */,
|
||||||
C74D76BC2C5D2DF300748DD1 /* FolderDetail.swift */,
|
C74D76BC2C5D2DF300748DD1 /* FolderDetail.swift */,
|
||||||
|
C72F4B6B2C5DF4ED00B04F60 /* SunburstChart.swift */,
|
||||||
);
|
);
|
||||||
path = PearDisk;
|
path = PearDisk;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -456,6 +459,7 @@
|
|||||||
C76D08482AF83C3F00D07867 /* Update.swift in Sources */,
|
C76D08482AF83C3F00D07867 /* Update.swift in Sources */,
|
||||||
C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */,
|
C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */,
|
||||||
C77B90162AF19377009CC655 /* AppState.swift in Sources */,
|
C77B90162AF19377009CC655 /* AppState.swift in Sources */,
|
||||||
|
C72F4B6C2C5DF4ED00B04F60 /* SunburstChart.swift in Sources */,
|
||||||
C74D76B72C5C4ABC00748DD1 /* TreeMap.swift in Sources */,
|
C74D76B72C5C4ABC00748DD1 /* TreeMap.swift in Sources */,
|
||||||
C74D76C02C5D2DF300748DD1 /* FolderDetail.swift in Sources */,
|
C74D76C02C5D2DF300748DD1 /* FolderDetail.swift in Sources */,
|
||||||
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */,
|
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"location" : "https://github.com/alienator88/AlinFoundation",
|
"location" : "https://github.com/alienator88/AlinFoundation",
|
||||||
"state" : {
|
"state" : {
|
||||||
"branch" : "main",
|
"branch" : "main",
|
||||||
"revision" : "986ac354d2a01f87ed4c37d19ce80a752a62fd4c"
|
"revision" : "705a7e5fe3c1374000d2fd29f1c58fa476b0453c"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ struct AppCommands: Commands {
|
|||||||
let fsm: FolderSettingsManager
|
let fsm: FolderSettingsManager
|
||||||
let updater: Updater
|
let updater: Updater
|
||||||
let themeManager: ThemeManager
|
let themeManager: ThemeManager
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
|
||||||
init(appState: AppState, locations: Locations, fsm: FolderSettingsManager, updater: Updater, themeManager: ThemeManager) {
|
init(appState: AppState, locations: Locations, fsm: FolderSettingsManager, updater: Updater, themeManager: ThemeManager) {
|
||||||
self.appState = appState
|
self.appState = appState
|
||||||
@@ -37,7 +38,7 @@ struct AppCommands: Commands {
|
|||||||
.keyboardShortcut("u", modifiers: .command)
|
.keyboardShortcut("u", modifiers: .command)
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
withAnimation(.easeInOut(duration: 0.5)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
reloadAppsList(appState: appState, fsm: fsm)
|
reloadAppsList(appState: appState, fsm: fsm)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ func listAppSupportDirectories() -> [String] {
|
|||||||
|
|
||||||
// Load app paths on launch
|
// Load app paths on launch
|
||||||
func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locations, fsm: FolderSettingsManager, completion: @escaping () -> Void = {}) {
|
func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locations, fsm: FolderSettingsManager, completion: @escaping () -> Void = {}) {
|
||||||
|
@AppStorage("settings.interface.animationEnabled") var animationEnabled: Bool = true
|
||||||
// appState.operationQueueLeftover.maxConcurrentOperationCount = 10 // Adjust this value as needed
|
// appState.operationQueueLeftover.maxConcurrentOperationCount = 10 // Adjust this value as needed
|
||||||
// appState.shouldCancelOperations = false
|
// appState.shouldCancelOperations = false
|
||||||
// appState.appInfoStore.removeAll()
|
// appState.appInfoStore.removeAll()
|
||||||
@@ -122,7 +123,7 @@ func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locatio
|
|||||||
updateOnMain {
|
updateOnMain {
|
||||||
printOS("Reverse search processed successfully")
|
printOS("Reverse search processed successfully")
|
||||||
appState.showProgress = false
|
appState.showProgress = false
|
||||||
withAnimation {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
appState.leftoverProgress.1 = 0.0
|
appState.leftoverProgress.1 = 0.0
|
||||||
}
|
}
|
||||||
appState.leftoverProgress.0 = "Reverse search completed successfully"
|
appState.leftoverProgress.0 = "Reverse search completed successfully"
|
||||||
@@ -218,6 +219,8 @@ func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locatio
|
|||||||
|
|
||||||
// Load item in Files view
|
// Load item in Files view
|
||||||
func showAppInFiles(appInfo: AppInfo, appState: AppState, locations: Locations, showPopover: Binding<Bool>) {
|
func showAppInFiles(appInfo: AppInfo, appState: AppState, locations: Locations, showPopover: Binding<Bool>) {
|
||||||
|
@AppStorage("settings.interface.animationEnabled") var animationEnabled: Bool = true
|
||||||
|
|
||||||
showPopover.wrappedValue = false
|
showPopover.wrappedValue = false
|
||||||
|
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
@@ -238,7 +241,7 @@ func showAppInFiles(appInfo: AppInfo, appState: AppState, locations: Locations,
|
|||||||
appState.appInfo = appInfo
|
appState.appInfo = appInfo
|
||||||
|
|
||||||
// Animate the view change and popover display.
|
// Animate the view change and popover display.
|
||||||
withAnimation(Animation.easeIn(duration: 0.4)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
appState.currentView = .files
|
appState.currentView = .files
|
||||||
showPopover.wrappedValue.toggle()
|
showPopover.wrappedValue.toggle()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class ReversePathsSearcher {
|
|||||||
|
|
||||||
guard !skipReverse.contains(where: { itemName.pearFormat().contains($0) }),
|
guard !skipReverse.contains(where: { itemName.pearFormat().contains($0) }),
|
||||||
isSupportedFileType(at: itemURL.path),
|
isSupportedFileType(at: itemURL.path),
|
||||||
!isRelatedToInstalledApp(itemPath: itemPath),
|
!isRelatedToInstalledApp(itemPath: itemPath, itemPathOG: itemURL),
|
||||||
!isExcludedByConditions(itemPath: itemPath) else {
|
!isExcludedByConditions(itemPath: itemPath) else {
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -80,9 +80,9 @@ class ReversePathsSearcher {
|
|||||||
collection.append(itemURL)
|
collection.append(itemURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func isRelatedToInstalledApp(itemPath: String) -> Bool {
|
private func isRelatedToInstalledApp(itemPath: String, itemPathOG: URL) -> Bool {
|
||||||
for app in sortedApps {
|
for app in sortedApps {
|
||||||
if itemPath.contains(app.bundleIdentifier.pearFormat()) || itemPath.contains(app.appName.pearFormat()) {
|
if itemPath.contains(app.bundleIdentifier.pearFormat()) || itemPath.contains(app.appName.pearFormat()) || itemPathOG.containerNameByUUID().pearFormat().contains(app.bundleIdentifier.pearFormat()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ struct ResetSettingsButtonStyle: ButtonStyle {
|
|||||||
let label: String
|
let label: String
|
||||||
let help: String
|
let help: String
|
||||||
@State private var hovered = false
|
@State private var hovered = false
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
@@ -35,7 +37,7 @@ struct ResetSettingsButtonStyle: ButtonStyle {
|
|||||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
||||||
.animation(.easeOut(duration: 0.2), value: configuration.isPressed)
|
.animation(.easeOut(duration: 0.2), value: configuration.isPressed)
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation(Animation.easeIn(duration: 0.15)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
hovered = hovering
|
hovered = hovering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +54,7 @@ struct ResetSettingsButtonStyle: ButtonStyle {
|
|||||||
struct SimpleCheckboxToggleStyle: ToggleStyle {
|
struct SimpleCheckboxToggleStyle: ToggleStyle {
|
||||||
@EnvironmentObject private var themeManager: ThemeManager
|
@EnvironmentObject private var themeManager: ThemeManager
|
||||||
@State private var isHovered: Bool = false
|
@State private var isHovered: Bool = false
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
@@ -66,7 +69,7 @@ struct SimpleCheckboxToggleStyle: ToggleStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
withAnimation(.smooth()) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
configuration.isOn.toggle()
|
configuration.isOn.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,6 +243,7 @@ struct GlassEffect: NSViewRepresentable {
|
|||||||
|
|
||||||
|
|
||||||
struct SimpleButtonBrightStyle: ButtonStyle {
|
struct SimpleButtonBrightStyle: ButtonStyle {
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
@State private var hovered = false
|
@State private var hovered = false
|
||||||
let icon: String
|
let icon: String
|
||||||
let label: String
|
let label: String
|
||||||
@@ -266,7 +270,7 @@ struct SimpleButtonBrightStyle: ButtonStyle {
|
|||||||
}
|
}
|
||||||
.padding(5)
|
.padding(5)
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation() {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
hovered = hovering
|
hovered = hovering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ func saveURLsToFile(urls: Set<URL>, appState: AppState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Open trash folder
|
// Open trash folder
|
||||||
func openTrash() {
|
func openTrash() {
|
||||||
if let trashURL = try? FileManager.default.url(for: .trashDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
|
if let trashURL = try? FileManager.default.url(for: .trashDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
|
||||||
@@ -240,6 +239,83 @@ func showLocalized(url: URL) -> String {
|
|||||||
return url.lastPathComponent
|
return url.lastPathComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
func localizedName() -> String? {
|
||||||
|
do {
|
||||||
|
let resourceValues = try self.resourceValues(forKeys: [.localizedNameKey])
|
||||||
|
return resourceValues.localizedName
|
||||||
|
} catch {
|
||||||
|
print("Error getting localized name: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
func localizedName() -> String? {
|
||||||
|
let url = URL(fileURLWithPath: self)
|
||||||
|
do {
|
||||||
|
let resourceValues = try url.resourceValues(forKeys: [.localizedNameKey])
|
||||||
|
return resourceValues.localizedName
|
||||||
|
} catch {
|
||||||
|
print("Error getting localized name: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension URL {
|
||||||
|
/// Returns the bundle name of the container by its UUID if found.
|
||||||
|
func containerNameByUUID() -> String {
|
||||||
|
// Extract the last path component, which should be the UUID
|
||||||
|
let uuid = self.lastPathComponent
|
||||||
|
|
||||||
|
// Ensure the UUID matches the expected pattern.
|
||||||
|
let uuidRegex = try! NSRegularExpression(
|
||||||
|
pattern: "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$",
|
||||||
|
options: .caseInsensitive
|
||||||
|
)
|
||||||
|
let range = NSRange(location: 0, length: uuid.utf16.count)
|
||||||
|
guard uuidRegex.firstMatch(in: uuid, options: [], range: range) != nil else {
|
||||||
|
// print("The URL does not point to a valid UUID container.")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path to the Containers directory.
|
||||||
|
let containersPath = FileManager.default.homeDirectoryForCurrentUser
|
||||||
|
.appendingPathComponent("Library/Containers")
|
||||||
|
|
||||||
|
do {
|
||||||
|
// List all directories in the Containers folder.
|
||||||
|
let containerDirectories = try FileManager.default.contentsOfDirectory(
|
||||||
|
at: containersPath,
|
||||||
|
includingPropertiesForKeys: nil,
|
||||||
|
options: .skipsHiddenFiles
|
||||||
|
)
|
||||||
|
|
||||||
|
// Iterate over each directory to find a match with the UUID.
|
||||||
|
for directory in containerDirectories {
|
||||||
|
let directoryName = directory.lastPathComponent
|
||||||
|
|
||||||
|
if directoryName == uuid {
|
||||||
|
// Attempt to read the metadata plist file.
|
||||||
|
let metadataPlistURL = directory.appendingPathComponent(".com.apple.containermanagerd.metadata.plist")
|
||||||
|
|
||||||
|
if let metadataDict = NSDictionary(contentsOf: metadataPlistURL),
|
||||||
|
let applicationBundleID = metadataDict["MCMMetadataIdentifier"] as? String {
|
||||||
|
return applicationBundleID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error accessing the Containers directory: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return nil if no matching UUID is found.
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return image for different folders
|
// Return image for different folders
|
||||||
func folderImages(for path: String) -> AnyView? {
|
func folderImages(for path: String) -> AnyView? {
|
||||||
if path.contains("/Library/Containers/") || path.contains("/Library/Group Containers/") {
|
if path.contains("/Library/Containers/") || path.contains("/Library/Group Containers/") {
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ struct FolderDetailView: View {
|
|||||||
self.childItems = items
|
self.childItems = items
|
||||||
self.currentPath = url
|
self.currentPath = url
|
||||||
self.currentItem = Item(url: url, name: url.lastPathComponent, size: items.reduce(0) { $0 + $1.size })
|
self.currentItem = Item(url: url, name: url.lastPathComponent, size: items.reduce(0) { $0 + $1.size })
|
||||||
|
print("Items loaded")
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
self.errorMessage = "Error loading contents: \(error.localizedDescription)"
|
self.errorMessage = "Error loading contents: \(error.localizedDescription)"
|
||||||
print("Error loading contents: \(error)")
|
print("Error loading contents: \(error)")
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ struct SidebarView: View {
|
|||||||
let basePaths = [
|
let basePaths = [
|
||||||
URL(fileURLWithPath: "/Volumes"),
|
URL(fileURLWithPath: "/Volumes"),
|
||||||
URL(fileURLWithPath: "/Users"),
|
URL(fileURLWithPath: "/Users"),
|
||||||
URL(fileURLWithPath: "/Users/alin"),
|
// URL(fileURLWithPath: "/Users/alin"),
|
||||||
|
URL(fileURLWithPath: "/"),
|
||||||
]
|
]
|
||||||
|
|
||||||
for basePath in basePaths {
|
for basePath in basePaths {
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
////
|
||||||
|
//// SunburstChart.swift
|
||||||
|
//// Pearcleaner
|
||||||
|
////
|
||||||
|
//// Created by Alin Lupascu on 8/2/24.
|
||||||
|
////
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//import SwiftUI
|
||||||
|
//import Charts
|
||||||
|
//
|
||||||
|
//final class Item: Identifiable {
|
||||||
|
// let id = UUID()
|
||||||
|
// let url: URL
|
||||||
|
// let name: String
|
||||||
|
// var size: Int64
|
||||||
|
// let isDirectory: Bool?
|
||||||
|
//
|
||||||
|
// init(url: URL, name: String, size: Int64, isDirectory: Bool = false) {
|
||||||
|
// self.url = url
|
||||||
|
// self.name = name
|
||||||
|
// self.size = size
|
||||||
|
// self.isDirectory = isDirectory
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//struct SunburstChart: View {
|
||||||
|
// let chartWidth: CGFloat = 175
|
||||||
|
// let items: [Item]
|
||||||
|
//
|
||||||
|
// @State private var selectedTotal1: Double?
|
||||||
|
// @State private var selectedSegment1: Item?
|
||||||
|
//
|
||||||
|
// @State private var selectedTotal2: Double?
|
||||||
|
// @State private var selectedSegment2: Item?
|
||||||
|
//
|
||||||
|
// init(items: [Item]) {
|
||||||
|
// self.items = items
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var body: some View {
|
||||||
|
// VStack {
|
||||||
|
// Text(selectedText)
|
||||||
|
// .font(.title)
|
||||||
|
// Text(selectedSizeText)
|
||||||
|
// .font(.title3)
|
||||||
|
//
|
||||||
|
// ZStack {
|
||||||
|
// Chart(getFiles()) { item in
|
||||||
|
// SectorMark(
|
||||||
|
// angle: .value("Size", Double(item.size)),
|
||||||
|
// innerRadius: .ratio(0.6),
|
||||||
|
// outerRadius: .ratio(0.9),
|
||||||
|
// angularInset: 1.5
|
||||||
|
// )
|
||||||
|
// .foregroundStyle(Color.blue.opacity(selectOpacity(item: item, selectedItem: selectedSegment2, ring: 2)))
|
||||||
|
// }
|
||||||
|
// .frame(width: chartWidth * 2, height: chartWidth * 2)
|
||||||
|
// .chartAngleSelection(value: $selectedTotal2)
|
||||||
|
// .onChange(of: selectedTotal2) { newValue in
|
||||||
|
// updateSelection(newValue: newValue, items: getFiles(), updateSelected: { selectedSegment2 = $0 })
|
||||||
|
// selectedSegment1 = nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Chart(getDirectories()) { item in
|
||||||
|
// SectorMark(
|
||||||
|
// angle: .value("Size", Double(item.size)),
|
||||||
|
// innerRadius: .ratio(0.5),
|
||||||
|
// angularInset: 1.5
|
||||||
|
// )
|
||||||
|
// .foregroundStyle(Color.green.opacity(selectOpacity(item: item, selectedItem: selectedSegment1)))
|
||||||
|
// }
|
||||||
|
// .frame(width: chartWidth, height: chartWidth)
|
||||||
|
// .chartAngleSelection(value: $selectedTotal1)
|
||||||
|
// .onChange(of: selectedTotal1) { newValue in
|
||||||
|
// updateSelection(newValue: newValue, items: getDirectories(), updateSelected: { selectedSegment1 = $0 })
|
||||||
|
// selectedSegment2 = nil
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private var selectedText: String {
|
||||||
|
// if let selectedItem = selectedSegment1 ?? selectedSegment2 {
|
||||||
|
// return selectedItem.name
|
||||||
|
// } else {
|
||||||
|
// return "File System Overview"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private var selectedSizeText: String {
|
||||||
|
// if let selectedItem = selectedSegment1 ?? selectedSegment2 {
|
||||||
|
// return ByteCountFormatter.string(fromByteCount: selectedItem.size, countStyle: .file)
|
||||||
|
// } else {
|
||||||
|
// return "Select a segment..."
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private func getDirectories() -> [Item] {
|
||||||
|
// return items.filter { $0.isDirectory == true }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private func getFiles() -> [Item] {
|
||||||
|
// return items.filter { $0.isDirectory == false }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private func selectOpacity(item: Item, selectedItem: Item?, ring: Int = 1) -> Double {
|
||||||
|
// if let selectedItem = selectedItem, selectedItem.id == item.id {
|
||||||
|
// return 1.0
|
||||||
|
// }
|
||||||
|
// return ring == 1 ? 0.8 : 0.55
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private func updateSelection(newValue: Double?, items: [Item], updateSelected: (Item?) -> Void) {
|
||||||
|
// guard let newValue = newValue else {
|
||||||
|
// updateSelected(nil)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var accumulatedSize: Double = 0
|
||||||
|
// let selectedItem = items.first { item in
|
||||||
|
// accumulatedSize += Double(item.size)
|
||||||
|
// return newValue <= accumulatedSize
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// updateSelected(selectedItem)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -15,6 +15,7 @@ struct TreeMapChart: View {
|
|||||||
let items: [Item]
|
let items: [Item]
|
||||||
var onItemSelected: (Item) -> Void
|
var onItemSelected: (Item) -> Void
|
||||||
@Binding var hoveredItem: Item?
|
@Binding var hoveredItem: Item?
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
|
||||||
var displayedItems: [Item] {
|
var displayedItems: [Item] {
|
||||||
items//.filter { $0.size >= 1_048_576 } // Filter to keep only items with size >= 1MB
|
items//.filter { $0.size >= 1_048_576 } // Filter to keep only items with size >= 1MB
|
||||||
@@ -46,6 +47,7 @@ struct TreeMapView: View {
|
|||||||
var onItemSelected: (Item) -> Void
|
var onItemSelected: (Item) -> Void
|
||||||
@Binding var hoveredItem: Item?
|
@Binding var hoveredItem: Item?
|
||||||
@State private var hoveredIndex: Int?
|
@State private var hoveredIndex: Int?
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
@@ -109,7 +111,7 @@ struct TreeMapView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onHover { isHovering in
|
.onHover { isHovering in
|
||||||
withAnimation {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
hoveredIndex = isHovering ? i : nil
|
hoveredIndex = isHovering ? i : nil
|
||||||
hoveredItem = isHovering ? file : nil
|
hoveredItem = isHovering ? file : nil
|
||||||
}
|
}
|
||||||
@@ -160,8 +162,6 @@ final class Item: Identifiable {
|
|||||||
let name: String
|
let name: String
|
||||||
var size: Int64
|
var size: Int64
|
||||||
let isDirectory: Bool?
|
let isDirectory: Bool?
|
||||||
// let parentURL: URL?
|
|
||||||
// let timestamp: Date
|
|
||||||
|
|
||||||
init(url: URL, name: String, size: Int64, isDirectory: Bool = false) {
|
init(url: URL, name: String, size: Int64, isDirectory: Bool = false) {
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ struct FolderSettingsTab: View {
|
|||||||
@EnvironmentObject var themeManager: ThemeManager
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@State private var isHovered = false
|
@State private var isHovered = false
|
||||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -54,7 +55,7 @@ struct FolderSettingsTab: View {
|
|||||||
}
|
}
|
||||||
.disabled(fsm.defaultPaths.contains(fsm.folderPaths[index]))
|
.disabled(fsm.defaultPaths.contains(fsm.folderPaths[index]))
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation(Animation.easeInOut(duration: 0.4)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
isHovered = hovering
|
isHovered = hovering
|
||||||
}
|
}
|
||||||
if isHovered && !fsm.defaultPaths.contains(fsm.folderPaths[index]) {
|
if isHovered && !fsm.defaultPaths.contains(fsm.folderPaths[index]) {
|
||||||
@@ -150,7 +151,7 @@ struct FolderSettingsTab: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation(Animation.easeInOut(duration: 0.4)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
isHovered = hovering
|
isHovered = hovering
|
||||||
}
|
}
|
||||||
if isHovered {
|
if isHovered {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ struct GeneralSettingsTab: View {
|
|||||||
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
|
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
|
||||||
@AppStorage("settings.general.brew") private var brew: Bool = false
|
@AppStorage("settings.general.brew") private var brew: Bool = false
|
||||||
@AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false
|
@AppStorage("settings.general.oneshot") private var oneShotMode: Bool = false
|
||||||
|
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
|
||||||
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
|
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
|
||||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||||
@State private var isResetting = false
|
@State private var isResetting = false
|
||||||
@@ -55,6 +56,30 @@ struct GeneralSettingsTab: View {
|
|||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: confirmAlert ? "exclamationmark.triangle.fill" : "exclamationmark.triangle")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("\(confirmAlert ? "Confirmation alerts enabled" : "Confirmation alerts disabled")")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoButton(text: "When deleting files using the Trash button, you can prevent accidental deletions by showing an alert before proceeding with the action")
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
Toggle(isOn: $confirmAlert, label: {
|
||||||
|
})
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Image(systemName: oneShotMode ? "scope" : "circlebadge")
|
Image(systemName: oneShotMode ? "scope" : "circlebadge")
|
||||||
.resizable()
|
.resizable()
|
||||||
@@ -234,7 +259,7 @@ struct GeneralSettingsTab: View {
|
|||||||
appState.updateExtensionStatus()
|
appState.updateExtensionStatus()
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 500)
|
.frame(width: 500, height: 520)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ struct InterfaceSettingsTab: View {
|
|||||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||||
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
||||||
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
@AppStorage("settings.general.miniview") private var miniView: Bool = true
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
@AppStorage("settings.interface.minimalist") private var minimalEnabled: Bool = true
|
||||||
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "bubbles.and.sparkles.fill"
|
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "bubbles.and.sparkles.fill"
|
||||||
@State private var isLaunchAtLoginEnabled: Bool = false
|
@State private var isLaunchAtLoginEnabled: Bool = false
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@@ -76,6 +78,48 @@ struct InterfaceSettingsTab: View {
|
|||||||
.padding(.leading)
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: animationEnabled ? "play" : "play.slash")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("\(animationEnabled ? "Animations enabled" : "Animations disabled")")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Toggle(isOn: $animationEnabled, label: {
|
||||||
|
})
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Image(systemName: minimalEnabled ? "list.dash.header.rectangle" : "list.bullet.rectangle")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(.trailing)
|
||||||
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
|
Text("\(minimalEnabled ? "Minimalist app list rows enabled" : "Minimalist app list rows disabled")")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Toggle(isOn: $minimalEnabled, label: {
|
||||||
|
})
|
||||||
|
.toggleStyle(.switch)
|
||||||
|
}
|
||||||
|
.padding(5)
|
||||||
|
.padding(.leading)
|
||||||
|
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Image(systemName: "paintbrush")
|
Image(systemName: "paintbrush")
|
||||||
.resizable()
|
.resizable()
|
||||||
@@ -380,7 +424,7 @@ struct InterfaceSettingsTab: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 500, height: 540)
|
.frame(width: 500, height: 600)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ struct AppListItems: View {
|
|||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
@AppStorage("settings.general.glass") private var glass: Bool = true
|
@AppStorage("settings.general.glass") private var glass: Bool = true
|
||||||
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
@AppStorage("settings.interface.minimalist") private var minimalEnabled: Bool = true
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@EnvironmentObject var locations: Locations
|
@EnvironmentObject var locations: Locations
|
||||||
let itemId = UUID()
|
let itemId = UUID()
|
||||||
@@ -30,7 +32,7 @@ struct AppListItems: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
ZStack() {
|
VStack() {
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
|
|
||||||
@@ -45,14 +47,39 @@ struct AppListItems: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 5) {
|
if minimalEnabled {
|
||||||
Text(appInfo.appName)
|
Text(appInfo.appName)
|
||||||
.font(.system(size: (isHovered || isSelected) ? 14 : 12))
|
.font(.system(size: (isSelected) ? 14 : 12))
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.truncationMode(.tail)
|
.truncationMode(.tail)
|
||||||
.padding(.horizontal, 5)
|
} else {
|
||||||
}
|
VStack(alignment: .center, spacing: 2) {
|
||||||
|
HStack {
|
||||||
|
Text(appInfo.appName)
|
||||||
|
.font(.system(size: (isSelected) ? 14 : 12))
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.tail)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 5) {
|
||||||
|
Text("v\(appInfo.appVersion)")
|
||||||
|
.font(.footnote)
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.tail)
|
||||||
|
.opacity(0.5)
|
||||||
|
Text("•").font(.footnote).opacity(0.5)
|
||||||
|
|
||||||
|
Text(bundleSize == 0 ? "calculating" : "\(formatByte(size: bundleSize).human)")
|
||||||
|
.font(.footnote)
|
||||||
|
.lineLimit(1)
|
||||||
|
.truncationMode(.tail)
|
||||||
|
.opacity(0.5)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -78,9 +105,13 @@ struct AppListItems: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(bundleSize == 0 ? "v\(appInfo.appVersion)" : (isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human))
|
if minimalEnabled {
|
||||||
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
|
Text(bundleSize == 0 ? "v\(appInfo.appVersion)" : (isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human))
|
||||||
.foregroundStyle(.primary.opacity(0.5))
|
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
|
||||||
|
.foregroundStyle(.primary.opacity(0.5))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,13 +121,13 @@ struct AppListItems: View {
|
|||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
.help(appInfo.appName)
|
.help(appInfo.appName)
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation(Animation.easeIn(duration: 0.3)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
self.isHovered = hovering
|
self.isHovered = hovering
|
||||||
self.hoveredItemPath = isHovered ? appInfo.path : nil
|
self.hoveredItemPath = isHovered ? appInfo.path : nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
withAnimation(Animation.easeInOut(duration: 0.4)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
if isSelected {
|
if isSelected {
|
||||||
appState.appInfo = .empty
|
appState.appInfo = .empty
|
||||||
appState.selectedItems = []
|
appState.selectedItems = []
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ struct AppSearchView: View {
|
|||||||
@State private var showMenu = false
|
@State private var showMenu = false
|
||||||
@Binding var isMenuBar: Bool
|
@Binding var isMenuBar: Bool
|
||||||
@AppStorage("settings.general.selectedSortAppsList") var selectedSortAlpha: Bool = true
|
@AppStorage("settings.general.selectedSortAppsList") var selectedSortAlpha: Bool = true
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -58,7 +59,7 @@ struct AppSearchView: View {
|
|||||||
|
|
||||||
if search.isEmpty {
|
if search.isEmpty {
|
||||||
Button("Refresh") {
|
Button("Refresh") {
|
||||||
withAnimation {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
showPopover = false
|
showPopover = false
|
||||||
reloadAppsList(appState: appState, fsm: fsm)
|
reloadAppsList(appState: appState, fsm: fsm)
|
||||||
}
|
}
|
||||||
@@ -78,7 +79,7 @@ struct AppSearchView: View {
|
|||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
|
||||||
Button("") {
|
Button("") {
|
||||||
withAnimation(.easeInOut(duration: 0.5)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
selectedSortAlpha.toggle()
|
selectedSortAlpha.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,7 @@ struct AppSearchView: View {
|
|||||||
|
|
||||||
if mini && !menubarEnabled {
|
if mini && !menubarEnabled {
|
||||||
Button("") {
|
Button("") {
|
||||||
withAnimation(.easeInOut(duration: 0.5)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
appState.currentView = .empty
|
appState.currentView = .empty
|
||||||
appState.appInfo = AppInfo.empty
|
appState.appInfo = AppInfo.empty
|
||||||
showPopover = false
|
showPopover = false
|
||||||
@@ -98,7 +99,7 @@ struct AppSearchView: View {
|
|||||||
|
|
||||||
Button("Leftover Files") {
|
Button("Leftover Files") {
|
||||||
showMenu = false
|
showMenu = false
|
||||||
withAnimation(.easeInOut(duration: 0.5)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
showPopover = false
|
showPopover = false
|
||||||
appState.appInfo = .empty
|
appState.appInfo = .empty
|
||||||
if appState.zombieFile.fileSize.keys.isEmpty {
|
if appState.zombieFile.fileSize.keys.isEmpty {
|
||||||
@@ -188,6 +189,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
|||||||
@EnvironmentObject var themeManager: ThemeManager
|
@EnvironmentObject var themeManager: ThemeManager
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
|
||||||
func _body(configuration: TextField<Self._Label>) -> some View {
|
func _body(configuration: TextField<Self._Label>) -> some View {
|
||||||
|
|
||||||
@@ -229,7 +231,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation(Animation.easeInOut(duration: 0.15)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
self.isHovered = hovering
|
self.isHovered = hovering
|
||||||
self.isFocused = true
|
self.isFocused = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ struct FilesView: View {
|
|||||||
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
|
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
|
||||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||||
@AppStorage("settings.general.filesWarning") private var warning: Bool = false
|
@AppStorage("settings.general.filesWarning") private var warning: Bool = false
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
|
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
|
||||||
@State private var showAlert = false
|
@State private var showAlert = false
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@@ -299,86 +301,88 @@ struct FilesView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|
||||||
InfoButton(text: "Always double-check the files/folders marked for removal. In some rare cases, Pearcleaner may find some unrelated files when app names are too similar.", color: .primary.opacity(0.5), warning: true, edge: .top)
|
InfoButton(text: "Always double-check the files/folders marked for removal. In some rare cases, Pearcleaner may find some unrelated files when app names are too similar.", color: .primary.opacity(0.5), warning: true, edge: .top)
|
||||||
.padding(.top)
|
.padding(.top)
|
||||||
|
|
||||||
|
|
||||||
Button("\(sizeType == "Logical" ? totalSelectedSize.logical : sizeType == "Finder" ? totalSelectedSize.finder : totalSelectedSize.real)") {
|
Button("\(sizeType == "Logical" ? totalSelectedSize.logical : sizeType == "Finder" ? totalSelectedSize.finder : totalSelectedSize.real)") {
|
||||||
Task {
|
showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: {
|
||||||
|
Task {
|
||||||
|
|
||||||
let selectedItemsArray = Array(appState.selectedItems)
|
let selectedItemsArray = Array(appState.selectedItems)
|
||||||
|
|
||||||
killApp(appId: appState.appInfo.bundleIdentifier) {
|
killApp(appId: appState.appInfo.bundleIdentifier) {
|
||||||
|
|
||||||
moveFilesToTrash(appState: appState, at: selectedItemsArray) { success in
|
|
||||||
|
|
||||||
// Send Sentinel FileWatcher start notification
|
moveFilesToTrash(appState: appState, at: selectedItemsArray) { success in
|
||||||
sendStartNotificationFW()
|
|
||||||
|
|
||||||
guard success else {
|
// Send Sentinel FileWatcher start notification
|
||||||
return
|
sendStartNotificationFW()
|
||||||
}
|
|
||||||
|
|
||||||
if appState.selectedItems.count == appState.appInfo.fileSize.keys.count {
|
guard success else {
|
||||||
updateOnMain {
|
return
|
||||||
search = ""
|
}
|
||||||
withAnimation {
|
|
||||||
if mini || menubarEnabled {
|
if appState.selectedItems.count == appState.appInfo.fileSize.keys.count {
|
||||||
appState.currentView = .apps
|
updateOnMain {
|
||||||
showPopover = false
|
search = ""
|
||||||
} else {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
appState.currentView = .empty
|
if mini || menubarEnabled {
|
||||||
|
appState.currentView = .apps
|
||||||
|
showPopover = false
|
||||||
|
} else {
|
||||||
|
appState.currentView = .empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Remove app from app list if main app bundle is removed for regular and wrapped apps
|
// Remove app from app list if main app bundle is removed for regular and wrapped apps
|
||||||
if (appState.appInfo.wrapped && selectedItemsArray.contains(where: { $0.absoluteString == appState.appInfo.path.deletingLastPathComponent().deletingLastPathComponent().absoluteString })) ||
|
if (appState.appInfo.wrapped && selectedItemsArray.contains(where: { $0.absoluteString == appState.appInfo.path.deletingLastPathComponent().deletingLastPathComponent().absoluteString })) ||
|
||||||
(!appState.appInfo.wrapped && selectedItemsArray.contains(where: { $0.absoluteString == appState.appInfo.path.absoluteString })) {
|
(!appState.appInfo.wrapped && selectedItemsArray.contains(where: { $0.absoluteString == appState.appInfo.path.absoluteString })) {
|
||||||
// Change view back to empty/apps if main app bundle is removed
|
// Change view back to empty/apps if main app bundle is removed
|
||||||
updateOnMain {
|
updateOnMain {
|
||||||
search = ""
|
search = ""
|
||||||
withAnimation {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
if mini || menubarEnabled {
|
if mini || menubarEnabled {
|
||||||
appState.currentView = .apps
|
appState.currentView = .apps
|
||||||
showPopover = false
|
showPopover = false
|
||||||
} else {
|
} else {
|
||||||
appState.currentView = .empty
|
appState.currentView = .empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// Match found, remove the app
|
||||||
// Match found, remove the app
|
removeApp(appState: appState, withPath: appState.appInfo.path)
|
||||||
removeApp(appState: appState, withPath: appState.appInfo.path)
|
|
||||||
|
|
||||||
if appState.oneShotMode {
|
|
||||||
NSApp.terminate(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Add deleted appInfo object to trashed array
|
|
||||||
appState.trashedFiles.append(appState.appInfo)
|
|
||||||
|
|
||||||
// Clear out appInfoStore object (Used for leftover file search)
|
|
||||||
// if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) {
|
|
||||||
// appState.appInfoStore[index] = .empty
|
|
||||||
// }
|
|
||||||
|
|
||||||
updateOnMain {
|
|
||||||
// Remove items from the list
|
|
||||||
appState.appInfo.fileSize = appState.appInfo.fileSize.filter { !appState.selectedItems.contains($0.key) }
|
|
||||||
// Update the selectedFiles to remove references that are no longer present
|
|
||||||
appState.selectedItems.removeAll()
|
|
||||||
if appState.oneShotMode {
|
if appState.oneShotMode {
|
||||||
NSApp.terminate(nil)
|
NSApp.terminate(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Add deleted appInfo object to trashed array
|
||||||
|
appState.trashedFiles.append(appState.appInfo)
|
||||||
|
|
||||||
|
// Clear out appInfoStore object (Used for leftover file search)
|
||||||
|
// if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) {
|
||||||
|
// appState.appInfoStore[index] = .empty
|
||||||
|
// }
|
||||||
|
|
||||||
|
updateOnMain {
|
||||||
|
// Remove items from the list
|
||||||
|
appState.appInfo.fileSize = appState.appInfo.fileSize.filter { !appState.selectedItems.contains($0.key) }
|
||||||
|
// Update the selectedFiles to remove references that are no longer present
|
||||||
|
appState.selectedItems.removeAll()
|
||||||
|
if appState.oneShotMode {
|
||||||
|
NSApp.terminate(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
.buttonStyle(UninstallButton(isEnabled: !appState.selectedItems.isEmpty))
|
.buttonStyle(UninstallButton(isEnabled: !appState.selectedItems.isEmpty))
|
||||||
@@ -434,6 +438,7 @@ struct FileDetailsItem: View {
|
|||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@State private var isHovered = false
|
@State private var isHovered = false
|
||||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
let size: Int64?
|
let size: Int64?
|
||||||
let sizeL: Int64?
|
let sizeL: Int64?
|
||||||
let icon: Image?
|
let icon: Image?
|
||||||
@@ -505,7 +510,7 @@ struct FileDetailsItem: View {
|
|||||||
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)
|
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)
|
||||||
}
|
}
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation(Animation.easeIn(duration: 0.2)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
self.isHovered = hovering
|
self.isHovered = hovering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ struct MiniMode: View {
|
|||||||
@State private var showUsr: Bool = true
|
@State private var showUsr: Bool = true
|
||||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||||
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
|
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ struct MiniEmptyView: View {
|
|||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@EnvironmentObject var locations: Locations
|
@EnvironmentObject var locations: Locations
|
||||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -86,7 +88,7 @@ struct MiniEmptyView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
withAnimation(.easeInOut(duration: 0.5)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
appState.currentView = .apps
|
appState.currentView = .apps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ struct ZombieView: View {
|
|||||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||||
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
|
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
|
||||||
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
|
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
|
||||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||||
|
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
@Binding var showPopover: Bool
|
@Binding var showPopover: Bool
|
||||||
@Binding var search: String
|
@Binding var search: String
|
||||||
@@ -266,10 +268,9 @@ struct ZombieView: View {
|
|||||||
.buttonStyle(RescanButton())
|
.buttonStyle(RescanButton())
|
||||||
|
|
||||||
Button("\(sizeType == "Logical" ? totalLogicalSizeUninstallBtn : sizeType == "Finder" ? totalFinderSizeUninstallBtn : totalRealSizeUninstallBtn)") {
|
Button("\(sizeType == "Logical" ? totalLogicalSizeUninstallBtn : sizeType == "Finder" ? totalFinderSizeUninstallBtn : totalRealSizeUninstallBtn)") {
|
||||||
|
showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: {
|
||||||
Task {
|
Task {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let selectedItemsArray = Array(selectedZombieItemsLocal)
|
let selectedItemsArray = Array(selectedZombieItemsLocal)
|
||||||
|
|
||||||
moveFilesToTrash(appState: appState, at: selectedItemsArray) { success in
|
moveFilesToTrash(appState: appState, at: selectedItemsArray) { success in
|
||||||
@@ -283,7 +284,7 @@ struct ZombieView: View {
|
|||||||
appState.zombieFile = .empty
|
appState.zombieFile = .empty
|
||||||
search = ""
|
search = ""
|
||||||
searchZ = ""
|
searchZ = ""
|
||||||
withAnimation {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
if mini || menubarEnabled {
|
if mini || menubarEnabled {
|
||||||
appState.currentView = .apps
|
appState.currentView = .apps
|
||||||
showPopover = false
|
showPopover = false
|
||||||
@@ -306,21 +307,12 @@ struct ZombieView: View {
|
|||||||
updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true)
|
updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateOnMain {
|
|
||||||
// // Remove items from the list
|
|
||||||
// appState.zombieFile.fileSize = appState.zombieFile.fileSize.filter { !selectedZombieItemsLocal.contains($0.key) }
|
|
||||||
// // Update the selectedZombieFiles to remove references that are no longer present
|
|
||||||
// selectedZombieItemsLocal.removeAll()
|
|
||||||
// updateTotalSizes()
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateMemoizedFiles(for: searchZ, sizeType: sizeType, selectedSortAlpha: selectedSortAlpha, force: true)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
.buttonStyle(UninstallButton(isEnabled: !selectedZombieItemsLocal.isEmpty))
|
.buttonStyle(UninstallButton(isEnabled: !selectedZombieItemsLocal.isEmpty))
|
||||||
.disabled(selectedZombieItemsLocal.isEmpty)
|
.disabled(selectedZombieItemsLocal.isEmpty)
|
||||||
@@ -472,6 +464,7 @@ struct ZombieFileDetailsItem: View {
|
|||||||
@EnvironmentObject var appState: AppState
|
@EnvironmentObject var appState: AppState
|
||||||
@State private var isHovered = false
|
@State private var isHovered = false
|
||||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||||
|
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||||
let size: Int64?
|
let size: Int64?
|
||||||
let sizeL: Int64?
|
let sizeL: Int64?
|
||||||
let icon: Image?
|
let icon: Image?
|
||||||
@@ -527,7 +520,7 @@ struct ZombieFileDetailsItem: View {
|
|||||||
.help(path.path)
|
.help(path.path)
|
||||||
}
|
}
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation(Animation.easeIn(duration: 0.2)) {
|
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||||
self.isHovered = hovering
|
self.isHovered = hovering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user