This commit is contained in:
Alin
2024-08-03 17:50:09 -06:00
parent 26f8478274
commit af53a77c76
19 changed files with 428 additions and 107 deletions
+4
View File
@@ -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"
} }
}, },
{ {
+2 -1
View File
@@ -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: {
+5 -2
View File
@@ -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()
} }
+3 -3
View File
@@ -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
} }
} }
+7 -3
View File
@@ -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
} }
} }
+77 -1
View File
@@ -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/") {
+1
View File
@@ -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)")
+2 -1
View File
@@ -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 {
+128
View File
@@ -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)
// }
//}
+3 -3
View File
@@ -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
+3 -2
View File
@@ -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 {
+26 -1
View File
@@ -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)
} }
+45 -1
View File
@@ -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)
} }
+41 -10
View File
@@ -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 = []
+7 -5
View File
@@ -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
} }
+62 -57
View File
@@ -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
} }
} }
+3 -1
View File
@@ -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
} }
} }
+8 -15
View File
@@ -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
} }
} }