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 */; };
|
||||
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.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 */; };
|
||||
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74412C62BBF249000DDFCA8 /* AppPathsFetch.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -220,6 +222,7 @@
|
||||
C74D76B92C5D2DF300748DD1 /* FileSearch.swift */,
|
||||
C74D76B62C5C4ABC00748DD1 /* TreeMap.swift */,
|
||||
C74D76BC2C5D2DF300748DD1 /* FolderDetail.swift */,
|
||||
C72F4B6B2C5DF4ED00B04F60 /* SunburstChart.swift */,
|
||||
);
|
||||
path = PearDisk;
|
||||
sourceTree = "<group>";
|
||||
@@ -456,6 +459,7 @@
|
||||
C76D08482AF83C3F00D07867 /* Update.swift in Sources */,
|
||||
C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */,
|
||||
C77B90162AF19377009CC655 /* AppState.swift in Sources */,
|
||||
C72F4B6C2C5DF4ED00B04F60 /* SunburstChart.swift in Sources */,
|
||||
C74D76B72C5C4ABC00748DD1 /* TreeMap.swift in Sources */,
|
||||
C74D76C02C5D2DF300748DD1 /* FolderDetail.swift in Sources */,
|
||||
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"location" : "https://github.com/alienator88/AlinFoundation",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "986ac354d2a01f87ed4c37d19ce80a752a62fd4c"
|
||||
"revision" : "705a7e5fe3c1374000d2fd29f1c58fa476b0453c"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ struct AppCommands: Commands {
|
||||
let fsm: FolderSettingsManager
|
||||
let updater: Updater
|
||||
let themeManager: ThemeManager
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
|
||||
init(appState: AppState, locations: Locations, fsm: FolderSettingsManager, updater: Updater, themeManager: ThemeManager) {
|
||||
self.appState = appState
|
||||
@@ -37,7 +38,7 @@ struct AppCommands: Commands {
|
||||
.keyboardShortcut("u", modifiers: .command)
|
||||
|
||||
Button {
|
||||
withAnimation(.easeInOut(duration: 0.5)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
reloadAppsList(appState: appState, fsm: fsm)
|
||||
}
|
||||
} label: {
|
||||
|
||||
@@ -110,6 +110,7 @@ func listAppSupportDirectories() -> [String] {
|
||||
|
||||
// Load app paths on launch
|
||||
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.shouldCancelOperations = false
|
||||
// appState.appInfoStore.removeAll()
|
||||
@@ -122,7 +123,7 @@ func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locatio
|
||||
updateOnMain {
|
||||
printOS("Reverse search processed successfully")
|
||||
appState.showProgress = false
|
||||
withAnimation {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
appState.leftoverProgress.1 = 0.0
|
||||
}
|
||||
appState.leftoverProgress.0 = "Reverse search completed successfully"
|
||||
@@ -218,6 +219,8 @@ func reversePreloader(allApps: [AppInfo], appState: AppState, locations: Locatio
|
||||
|
||||
// Load item in Files view
|
||||
func showAppInFiles(appInfo: AppInfo, appState: AppState, locations: Locations, showPopover: Binding<Bool>) {
|
||||
@AppStorage("settings.interface.animationEnabled") var animationEnabled: Bool = true
|
||||
|
||||
showPopover.wrappedValue = false
|
||||
|
||||
updateOnMain {
|
||||
@@ -238,7 +241,7 @@ func showAppInFiles(appInfo: AppInfo, appState: AppState, locations: Locations,
|
||||
appState.appInfo = appInfo
|
||||
|
||||
// Animate the view change and popover display.
|
||||
withAnimation(Animation.easeIn(duration: 0.4)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
appState.currentView = .files
|
||||
showPopover.wrappedValue.toggle()
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class ReversePathsSearcher {
|
||||
|
||||
guard !skipReverse.contains(where: { itemName.pearFormat().contains($0) }),
|
||||
isSupportedFileType(at: itemURL.path),
|
||||
!isRelatedToInstalledApp(itemPath: itemPath),
|
||||
!isRelatedToInstalledApp(itemPath: itemPath, itemPathOG: itemURL),
|
||||
!isExcludedByConditions(itemPath: itemPath) else {
|
||||
|
||||
return
|
||||
@@ -80,9 +80,9 @@ class ReversePathsSearcher {
|
||||
collection.append(itemURL)
|
||||
}
|
||||
|
||||
private func isRelatedToInstalledApp(itemPath: String) -> Bool {
|
||||
private func isRelatedToInstalledApp(itemPath: String, itemPathOG: URL) -> Bool {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ struct ResetSettingsButtonStyle: ButtonStyle {
|
||||
let label: String
|
||||
let help: String
|
||||
@State private var hovered = false
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
HStack(alignment: .center) {
|
||||
@@ -35,7 +37,7 @@ struct ResetSettingsButtonStyle: ButtonStyle {
|
||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
||||
.animation(.easeOut(duration: 0.2), value: configuration.isPressed)
|
||||
.onHover { hovering in
|
||||
withAnimation(Animation.easeIn(duration: 0.15)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
hovered = hovering
|
||||
}
|
||||
}
|
||||
@@ -52,6 +54,7 @@ struct ResetSettingsButtonStyle: ButtonStyle {
|
||||
struct SimpleCheckboxToggleStyle: ToggleStyle {
|
||||
@EnvironmentObject private var themeManager: ThemeManager
|
||||
@State private var isHovered: Bool = false
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
HStack {
|
||||
@@ -66,7 +69,7 @@ struct SimpleCheckboxToggleStyle: ToggleStyle {
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
withAnimation(.smooth()) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
configuration.isOn.toggle()
|
||||
}
|
||||
}
|
||||
@@ -240,6 +243,7 @@ struct GlassEffect: NSViewRepresentable {
|
||||
|
||||
|
||||
struct SimpleButtonBrightStyle: ButtonStyle {
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
@State private var hovered = false
|
||||
let icon: String
|
||||
let label: String
|
||||
@@ -266,7 +270,7 @@ struct SimpleButtonBrightStyle: ButtonStyle {
|
||||
}
|
||||
.padding(5)
|
||||
.onHover { hovering in
|
||||
withAnimation() {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
hovered = hovering
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,6 @@ func saveURLsToFile(urls: Set<URL>, appState: AppState) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Open trash folder
|
||||
func openTrash() {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
func folderImages(for path: String) -> AnyView? {
|
||||
if path.contains("/Library/Containers/") || path.contains("/Library/Group Containers/") {
|
||||
|
||||
@@ -182,6 +182,7 @@ struct FolderDetailView: View {
|
||||
self.childItems = items
|
||||
self.currentPath = url
|
||||
self.currentItem = Item(url: url, name: url.lastPathComponent, size: items.reduce(0) { $0 + $1.size })
|
||||
print("Items loaded")
|
||||
case .failure(let error):
|
||||
self.errorMessage = "Error loading contents: \(error.localizedDescription)"
|
||||
print("Error loading contents: \(error)")
|
||||
|
||||
@@ -57,7 +57,8 @@ struct SidebarView: View {
|
||||
let basePaths = [
|
||||
URL(fileURLWithPath: "/Volumes"),
|
||||
URL(fileURLWithPath: "/Users"),
|
||||
URL(fileURLWithPath: "/Users/alin"),
|
||||
// URL(fileURLWithPath: "/Users/alin"),
|
||||
URL(fileURLWithPath: "/"),
|
||||
]
|
||||
|
||||
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]
|
||||
var onItemSelected: (Item) -> Void
|
||||
@Binding var hoveredItem: Item?
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
|
||||
var displayedItems: [Item] {
|
||||
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
|
||||
@Binding var hoveredItem: Item?
|
||||
@State private var hoveredIndex: Int?
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
@@ -109,7 +111,7 @@ struct TreeMapView: View {
|
||||
}
|
||||
}
|
||||
.onHover { isHovering in
|
||||
withAnimation {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
hoveredIndex = isHovering ? i : nil
|
||||
hoveredItem = isHovering ? file : nil
|
||||
}
|
||||
@@ -160,8 +162,6 @@ final class Item: Identifiable {
|
||||
let name: String
|
||||
var size: Int64
|
||||
let isDirectory: Bool?
|
||||
// let parentURL: URL?
|
||||
// let timestamp: Date
|
||||
|
||||
init(url: URL, name: String, size: Int64, isDirectory: Bool = false) {
|
||||
self.url = url
|
||||
|
||||
@@ -18,6 +18,7 @@ struct FolderSettingsTab: View {
|
||||
@EnvironmentObject var themeManager: ThemeManager
|
||||
@State private var isHovered = false
|
||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
|
||||
|
||||
var body: some View {
|
||||
@@ -54,7 +55,7 @@ struct FolderSettingsTab: View {
|
||||
}
|
||||
.disabled(fsm.defaultPaths.contains(fsm.folderPaths[index]))
|
||||
.onHover { hovering in
|
||||
withAnimation(Animation.easeInOut(duration: 0.4)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
isHovered = hovering
|
||||
}
|
||||
if isHovered && !fsm.defaultPaths.contains(fsm.folderPaths[index]) {
|
||||
@@ -150,7 +151,7 @@ struct FolderSettingsTab: View {
|
||||
Spacer()
|
||||
}
|
||||
.onHover { hovering in
|
||||
withAnimation(Animation.easeInOut(duration: 0.4)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
isHovered = hovering
|
||||
}
|
||||
if isHovered {
|
||||
|
||||
@@ -17,6 +17,7 @@ struct GeneralSettingsTab: View {
|
||||
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
|
||||
@AppStorage("settings.general.brew") private var brew: 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.sizeType") var sizeType: String = "Real"
|
||||
@State private var isResetting = false
|
||||
@@ -55,6 +56,30 @@ struct GeneralSettingsTab: View {
|
||||
.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) {
|
||||
Image(systemName: oneShotMode ? "scope" : "circlebadge")
|
||||
.resizable()
|
||||
@@ -234,7 +259,7 @@ struct GeneralSettingsTab: View {
|
||||
appState.updateExtensionStatus()
|
||||
}
|
||||
.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.popover") private var popoverStay: 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"
|
||||
@State private var isLaunchAtLoginEnabled: Bool = false
|
||||
@Binding var showPopover: Bool
|
||||
@@ -76,6 +78,48 @@ struct InterfaceSettingsTab: View {
|
||||
.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) {
|
||||
Image(systemName: "paintbrush")
|
||||
.resizable()
|
||||
@@ -380,7 +424,7 @@ struct InterfaceSettingsTab: View {
|
||||
|
||||
}
|
||||
.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.glass") private var glass: Bool = true
|
||||
@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
|
||||
@EnvironmentObject var locations: Locations
|
||||
let itemId = UUID()
|
||||
@@ -30,7 +32,7 @@ struct AppListItems: View {
|
||||
|
||||
var body: some View {
|
||||
|
||||
ZStack() {
|
||||
VStack() {
|
||||
|
||||
HStack(alignment: .center) {
|
||||
|
||||
@@ -45,14 +47,39 @@ struct AppListItems: View {
|
||||
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
if minimalEnabled {
|
||||
Text(appInfo.appName)
|
||||
.font(.system(size: (isHovered || isSelected) ? 14 : 12))
|
||||
.font(.system(size: (isSelected) ? 14 : 12))
|
||||
.lineLimit(1)
|
||||
.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()
|
||||
@@ -78,9 +105,13 @@ struct AppListItems: View {
|
||||
)
|
||||
}
|
||||
|
||||
if minimalEnabled {
|
||||
Text(bundleSize == 0 ? "v\(appInfo.appVersion)" : (isHovered ? "v\(appInfo.appVersion)" : formatByte(size: bundleSize).human))
|
||||
.font(.system(size: (isHovered || isSelected) ? 12 : 10))
|
||||
.foregroundStyle(.primary.opacity(0.5))
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -90,13 +121,13 @@ struct AppListItems: View {
|
||||
.padding(.vertical, 5)
|
||||
.help(appInfo.appName)
|
||||
.onHover { hovering in
|
||||
withAnimation(Animation.easeIn(duration: 0.3)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
self.isHovered = hovering
|
||||
self.hoveredItemPath = isHovered ? appInfo.path : nil
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
withAnimation(Animation.easeInOut(duration: 0.4)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
if isSelected {
|
||||
appState.appInfo = .empty
|
||||
appState.selectedItems = []
|
||||
|
||||
@@ -23,6 +23,7 @@ struct AppSearchView: View {
|
||||
@State private var showMenu = false
|
||||
@Binding var isMenuBar: Bool
|
||||
@AppStorage("settings.general.selectedSortAppsList") var selectedSortAlpha: Bool = true
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
|
||||
|
||||
var body: some View {
|
||||
@@ -58,7 +59,7 @@ struct AppSearchView: View {
|
||||
|
||||
if search.isEmpty {
|
||||
Button("Refresh") {
|
||||
withAnimation {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
showPopover = false
|
||||
reloadAppsList(appState: appState, fsm: fsm)
|
||||
}
|
||||
@@ -78,7 +79,7 @@ struct AppSearchView: View {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
Button("") {
|
||||
withAnimation(.easeInOut(duration: 0.5)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
selectedSortAlpha.toggle()
|
||||
}
|
||||
}
|
||||
@@ -86,7 +87,7 @@ struct AppSearchView: View {
|
||||
|
||||
if mini && !menubarEnabled {
|
||||
Button("") {
|
||||
withAnimation(.easeInOut(duration: 0.5)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
appState.currentView = .empty
|
||||
appState.appInfo = AppInfo.empty
|
||||
showPopover = false
|
||||
@@ -98,7 +99,7 @@ struct AppSearchView: View {
|
||||
|
||||
Button("Leftover Files") {
|
||||
showMenu = false
|
||||
withAnimation(.easeInOut(duration: 0.5)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
showPopover = false
|
||||
appState.appInfo = .empty
|
||||
if appState.zombieFile.fileSize.keys.isEmpty {
|
||||
@@ -188,6 +189,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
||||
@EnvironmentObject var themeManager: ThemeManager
|
||||
@AppStorage("settings.general.mini") private var mini: 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 {
|
||||
|
||||
@@ -229,7 +231,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
||||
|
||||
}
|
||||
.onHover { hovering in
|
||||
withAnimation(Animation.easeInOut(duration: 0.15)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
self.isHovered = hovering
|
||||
self.isFocused = true
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ struct FilesView: View {
|
||||
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
|
||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||
@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
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Binding var showPopover: Bool
|
||||
@@ -299,12 +301,12 @@ struct FilesView: View {
|
||||
|
||||
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)
|
||||
.padding(.top)
|
||||
|
||||
|
||||
Button("\(sizeType == "Logical" ? totalSelectedSize.logical : sizeType == "Finder" ? totalSelectedSize.finder : totalSelectedSize.real)") {
|
||||
showCustomAlert(enabled: confirmAlert, title: "Warning", message: "Are you sure you want to remove these files?", style: .warning, onOk: {
|
||||
Task {
|
||||
|
||||
let selectedItemsArray = Array(appState.selectedItems)
|
||||
@@ -323,7 +325,7 @@ struct FilesView: View {
|
||||
if appState.selectedItems.count == appState.appInfo.fileSize.keys.count {
|
||||
updateOnMain {
|
||||
search = ""
|
||||
withAnimation {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
if mini || menubarEnabled {
|
||||
appState.currentView = .apps
|
||||
showPopover = false
|
||||
@@ -340,7 +342,7 @@ struct FilesView: View {
|
||||
// Change view back to empty/apps if main app bundle is removed
|
||||
updateOnMain {
|
||||
search = ""
|
||||
withAnimation {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
if mini || menubarEnabled {
|
||||
appState.currentView = .apps
|
||||
showPopover = false
|
||||
@@ -361,9 +363,9 @@ struct FilesView: View {
|
||||
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
|
||||
// }
|
||||
// if let index = appState.appInfoStore.firstIndex(where: { $0.path == appState.appInfo.path }) {
|
||||
// appState.appInfoStore[index] = .empty
|
||||
// }
|
||||
|
||||
updateOnMain {
|
||||
// Remove items from the list
|
||||
@@ -379,6 +381,8 @@ struct FilesView: View {
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
.buttonStyle(UninstallButton(isEnabled: !appState.selectedItems.isEmpty))
|
||||
@@ -434,6 +438,7 @@ struct FileDetailsItem: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@State private var isHovered = false
|
||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
let size: Int64?
|
||||
let sizeL: Int64?
|
||||
let icon: Image?
|
||||
@@ -505,7 +510,7 @@ struct FileDetailsItem: View {
|
||||
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)
|
||||
}
|
||||
.onHover { hovering in
|
||||
withAnimation(Animation.easeIn(duration: 0.2)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
self.isHovered = hovering
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ struct MiniMode: View {
|
||||
@State private var showUsr: Bool = true
|
||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
@Binding var showPopover: Bool
|
||||
|
||||
|
||||
@@ -54,6 +55,7 @@ struct MiniEmptyView: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@EnvironmentObject var locations: Locations
|
||||
@AppStorage("settings.general.mini") private var mini: Bool = false
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
@Binding var showPopover: Bool
|
||||
|
||||
var body: some View {
|
||||
@@ -86,7 +88,7 @@ struct MiniEmptyView: View {
|
||||
|
||||
}
|
||||
.onTapGesture {
|
||||
withAnimation(.easeInOut(duration: 0.5)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
appState.currentView = .apps
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,10 @@ struct ZombieView: View {
|
||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||
@AppStorage("settings.sentinel.enable") private var sentinel: 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.sizeType") var sizeType: String = "Real"
|
||||
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Binding var showPopover: Bool
|
||||
@Binding var search: String
|
||||
@@ -266,10 +268,9 @@ struct ZombieView: View {
|
||||
.buttonStyle(RescanButton())
|
||||
|
||||
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 {
|
||||
|
||||
|
||||
|
||||
let selectedItemsArray = Array(selectedZombieItemsLocal)
|
||||
|
||||
moveFilesToTrash(appState: appState, at: selectedItemsArray) { success in
|
||||
@@ -283,7 +284,7 @@ struct ZombieView: View {
|
||||
appState.zombieFile = .empty
|
||||
search = ""
|
||||
searchZ = ""
|
||||
withAnimation {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
if mini || menubarEnabled {
|
||||
appState.currentView = .apps
|
||||
showPopover = false
|
||||
@@ -306,20 +307,11 @@ struct ZombieView: View {
|
||||
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))
|
||||
@@ -472,6 +464,7 @@ struct ZombieFileDetailsItem: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@State private var isHovered = false
|
||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||
@AppStorage("settings.interface.animationEnabled") private var animationEnabled: Bool = true
|
||||
let size: Int64?
|
||||
let sizeL: Int64?
|
||||
let icon: Image?
|
||||
@@ -527,7 +520,7 @@ struct ZombieFileDetailsItem: View {
|
||||
.help(path.path)
|
||||
}
|
||||
.onHover { hovering in
|
||||
withAnimation(Animation.easeIn(duration: 0.2)) {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
self.isHovered = hovering
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user