From bf7771cf616dd2a9192738553b1fd3b699f0cb8f Mon Sep 17 00:00:00 2001 From: Alin Date: Fri, 12 Apr 2024 16:44:53 -0600 Subject: [PATCH] v3.5.0 --- FinderOpen/FinderOpen.entitlements | 14 ++ FinderOpen/FinderOpen.swift | 131 ++++++++++++ FinderOpen/Info.plist | 19 ++ Pearcleaner.xcodeproj/project.pbxproj | 187 +++++++++++++++++- .../xcschemes/FinderOpen.xcscheme | 101 ++++++++++ Pearcleaner/Logic/AppCommands.swift | 9 +- Pearcleaner/Logic/AppState.swift | 17 ++ Pearcleaner/Logic/DeepLink.swift | 1 - Pearcleaner/Logic/Styles.swift | 141 +++++++------ Pearcleaner/Logic/ThemeManager.swift | 59 ++++++ Pearcleaner/PearcleanerApp.swift | 4 + Pearcleaner/Resources/Info.plist | 4 + Pearcleaner/Settings/About.swift | 16 +- Pearcleaner/Settings/General.swift | 88 +++++---- Pearcleaner/Settings/Interface.swift | 143 +++++++++----- Pearcleaner/Settings/SettingsWindow.swift | 4 +- Pearcleaner/Settings/Update.swift | 10 +- Pearcleaner/Views/AppListItems.swift | 76 ++++--- Pearcleaner/Views/AppsListView.swift | 4 + Pearcleaner/Views/FilesView.swift | 46 ++--- Pearcleaner/Views/MenuBarMiniAppView.swift | 101 +++++----- Pearcleaner/Views/MiniMode.swift | 60 +++--- Pearcleaner/Views/RegularMode.swift | 125 +++++------- Pearcleaner/Views/TopBar.swift | 13 +- Pearcleaner/Views/TopBarMini.swift | 8 +- Pearcleaner/Views/ZombieView.swift | 44 ++--- Pearcleaner/Windows/PermView.swift | 12 +- features.json | 1 + 28 files changed, 1020 insertions(+), 418 deletions(-) create mode 100644 FinderOpen/FinderOpen.entitlements create mode 100644 FinderOpen/FinderOpen.swift create mode 100644 FinderOpen/Info.plist create mode 100644 Pearcleaner.xcodeproj/xcshareddata/xcschemes/FinderOpen.xcscheme create mode 100644 Pearcleaner/Logic/ThemeManager.swift diff --git a/FinderOpen/FinderOpen.entitlements b/FinderOpen/FinderOpen.entitlements new file mode 100644 index 0000000..d1b27e8 --- /dev/null +++ b/FinderOpen/FinderOpen.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + com.apple.security.temporary-exception.files.home-relative-path.read-write + + / + + + diff --git a/FinderOpen/FinderOpen.swift b/FinderOpen/FinderOpen.swift new file mode 100644 index 0000000..6ed0901 --- /dev/null +++ b/FinderOpen/FinderOpen.swift @@ -0,0 +1,131 @@ +// +// FinderSync.swift +// FinderOpen +// +// Created by Alin Lupascu on 4/11/24. +// + +import Cocoa +import FinderSync + +class FinderOpen: FIFinderSync { + + override init() { + super.init() + NSLog("FinderSync() launched from %@", Bundle.main.bundlePath as NSString) + // Set the directory URLs that the Finder Sync extension observes + FIFinderSyncController.default().directoryURLs = Set([URL(fileURLWithPath: "/")]) + } + + override func menu(for menuKind: FIMenuKind) -> NSMenu { + let menu = NSMenu(title: "") + + // Ensure we are dealing with the contextual menu for items + if menuKind == .contextualMenuForItems { + // Get the selected items + if let selectedItemURLs = FIFinderSyncController.default().selectedItemURLs(), + selectedItemURLs.count == 1, selectedItemURLs.first?.pathExtension == "app" { + // Add menu item if the selected item is a .app file + let menuItem = NSMenuItem(title: "Pearcleaner Uninstall", action: #selector(openInMyApp), keyEquivalent: "") +// menuItem.image = NSImage(named: NSImage.trashEmptyName) +// menu.addItem(withTitle: "Pearcleaner Uninstall", action: #selector(openInMyApp), keyEquivalent: "") + menu.addItem(menuItem) + } + } + + // Return the menu (which may be empty if the conditions are not met) + return menu + + } + + @objc func openInMyApp(_ sender: AnyObject?) { + // Get the selected items (files/folders) in Finder + guard let selectedItems = FIFinderSyncController.default().selectedItemURLs(), !selectedItems.isEmpty else { + return + } + + // Consider only the first selected item + let firstSelectedItem = selectedItems[0] + let path = firstSelectedItem.path + NSWorkspace.shared.open(URL(string: "pear://com.alienator88.Pearcleaner?path=\(path)")!) + + } + + +} + + +//class FinderSync: FIFinderSync { +// +// var myFolderURL = URL(fileURLWithPath: "/Users/Shared/MySyncExtension Documents") +// +// override init() { +// super.init() +// +// NSLog("FinderSync() launched from %@", Bundle.main.bundlePath as NSString) +// +// // Set up the directory we are syncing. +// FIFinderSyncController.default().directoryURLs = [self.myFolderURL] +// +// // Set up images for our badge identifiers. For demonstration purposes, this uses off-the-shelf images. +// FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.colorPanelName)!, label: "Status One" , forBadgeIdentifier: "One") +// FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.cautionName)!, label: "Status Two", forBadgeIdentifier: "Two") +// } +// +// // MARK: - Primary Finder Sync protocol methods +// +// override func beginObservingDirectory(at url: URL) { +// // The user is now seeing the container's contents. +// // If they see it in more than one view at a time, we're only told once. +// NSLog("beginObservingDirectoryAtURL: %@", url.path as NSString) +// } +// +// +// override func endObservingDirectory(at url: URL) { +// // The user is no longer seeing the container's contents. +// NSLog("endObservingDirectoryAtURL: %@", url.path as NSString) +// } +// +// override func requestBadgeIdentifier(for url: URL) { +// NSLog("requestBadgeIdentifierForURL: %@", url.path as NSString) +// +// // For demonstration purposes, this picks one of our two badges, or no badge at all, based on the filename. +// let whichBadge = abs(url.path.hash) % 3 +// let badgeIdentifier = ["", "One", "Two"][whichBadge] +// FIFinderSyncController.default().setBadgeIdentifier(badgeIdentifier, for: url) +// } +// +// // MARK: - Menu and toolbar item support +// +// override var toolbarItemName: String { +// return "FinderSy" +// } +// +// override var toolbarItemToolTip: String { +// return "FinderSy: Click the toolbar item for a menu." +// } +// +// override var toolbarItemImage: NSImage { +// return NSImage(named: NSImage.cautionName)! +// } +// +// override func menu(for menuKind: FIMenuKind) -> NSMenu { +// // Produce a menu for the extension. +// let menu = NSMenu(title: "") +// menu.addItem(withTitle: "Example Menu Item", action: #selector(sampleAction(_:)), keyEquivalent: "") +// return menu +// } +// +// @IBAction func sampleAction(_ sender: AnyObject?) { +// let target = FIFinderSyncController.default().targetedURL() +// let items = FIFinderSyncController.default().selectedItemURLs() +// +// let item = sender as! NSMenuItem +// NSLog("sampleAction: menu item: %@, target = %@, items = ", item.title as NSString, target!.path as NSString) +// for obj in items! { +// NSLog(" %@", obj.path as NSString) +// } +// } +// +//} + diff --git a/FinderOpen/Info.plist b/FinderOpen/Info.plist new file mode 100644 index 0000000..421c491 --- /dev/null +++ b/FinderOpen/Info.plist @@ -0,0 +1,19 @@ + + + + + CFBundleShortVersionString + $(APP_VERSION) + CFBundleVersion + $(APP_BUILD) + NSExtension + + NSExtensionAttributes + + NSExtensionPointIdentifier + com.apple.FinderSync + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).FinderOpen + + + diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index b9e0e53..31e62b5 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -29,6 +29,9 @@ C77B901C2AF193A3009CC655 /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B901B2AF193A3009CC655 /* Styles.swift */; }; C77B901E2AF1A9DB009CC655 /* SettingsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B901D2AF1A9DB009CC655 /* SettingsWindow.swift */; }; C77B90232AF2D616009CC655 /* FilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90222AF2D616009CC655 /* FilesView.swift */; }; + C78121662BC892A000BE06BD /* FinderOpen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C78121652BC892A000BE06BD /* FinderOpen.swift */; }; + C781216B2BC892A000BE06BD /* FinderOpen.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C78121632BC892A000BE06BD /* FinderOpen.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + C79D05202BC99EB40083F976 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79D051F2BC99EB40083F976 /* ThemeManager.swift */; }; C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */; }; C7A2AE5D2B9644F300161540 /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7A2AE5C2B9644F300161540 /* FileWatcher */; }; C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A9CE462B89164700EB6E78 /* Authorization.swift */; }; @@ -49,6 +52,16 @@ C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7FB173A2B96321300B96F9A /* AppsListView.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + C78121692BC892A000BE06BD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C77B8FF82AF18E2E009CC655 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C78121622BC892A000BE06BD; + remoteInfo = FinderOpen; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ C728930D2AFD51EA00C8C1CD /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -59,6 +72,17 @@ ); runOnlyForDeploymentPostprocessing = 1; }; + C781216C2BC892A000BE06BD /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + C781216B2BC892A000BE06BD /* FinderOpen.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; C7A27E7D2AFD65C400166168 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -97,6 +121,11 @@ C77B901D2AF1A9DB009CC655 /* SettingsWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindow.swift; sourceTree = ""; }; C77B90222AF2D616009CC655 /* FilesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesView.swift; sourceTree = ""; }; C77B90242AF2D796009CC655 /* Pearcleaner.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Pearcleaner.entitlements; sourceTree = ""; }; + C78121632BC892A000BE06BD /* FinderOpen.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FinderOpen.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + C78121652BC892A000BE06BD /* FinderOpen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinderOpen.swift; sourceTree = ""; }; + C78121672BC892A000BE06BD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C78121682BC892A000BE06BD /* FinderOpen.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FinderOpen.entitlements; sourceTree = ""; }; + C79D051F2BC99EB40083F976 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; C7A27E7F2AFD70DD00166168 /* PearcleanerSentinel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PearcleanerSentinel.entitlements; sourceTree = ""; }; C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.alienator88.PearcleanerSentinel.plist; sourceTree = ""; }; C7A9CE462B89164700EB6E78 /* Authorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authorization.swift; sourceTree = ""; }; @@ -133,6 +162,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C78121602BC892A000BE06BD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -164,6 +200,7 @@ C7A9CE462B89164700EB6E78 /* Authorization.swift */, C7ACADE52B92A737000B5845 /* Features.swift */, C7ED86B22BA8ED8800F13DE4 /* Trash.swift */, + C79D051F2BC99EB40083F976 /* ThemeManager.swift */, ); path = Logic; sourceTree = ""; @@ -214,6 +251,7 @@ children = ( C77B90022AF18E2E009CC655 /* Pearcleaner */, C72893102AFD51EA00C8C1CD /* PearcleanerSentinel */, + C78121642BC892A000BE06BD /* FinderOpen */, C77B90012AF18E2E009CC655 /* Products */, C72893162AFD531500C8C1CD /* Frameworks */, ); @@ -224,6 +262,7 @@ children = ( C77B90002AF18E2E009CC655 /* Pearcleaner.app */, C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */, + C78121632BC892A000BE06BD /* FinderOpen.appex */, ); name = Products; sourceTree = ""; @@ -257,6 +296,16 @@ path = Views; sourceTree = ""; }; + C78121642BC892A000BE06BD /* FinderOpen */ = { + isa = PBXGroup; + children = ( + C78121652BC892A000BE06BD /* FinderOpen.swift */, + C78121672BC892A000BE06BD /* Info.plist */, + C78121682BC892A000BE06BD /* FinderOpen.entitlements */, + ); + path = FinderOpen; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -288,10 +337,12 @@ C77B8FFD2AF18E2E009CC655 /* Frameworks */, C77B8FFE2AF18E2E009CC655 /* Resources */, C7A27E7D2AFD65C400166168 /* CopyFiles */, + C781216C2BC892A000BE06BD /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + C781216A2BC892A000BE06BD /* PBXTargetDependency */, ); name = Pearcleaner; packageProductDependencies = ( @@ -300,6 +351,23 @@ productReference = C77B90002AF18E2E009CC655 /* Pearcleaner.app */; productType = "com.apple.product-type.application"; }; + C78121622BC892A000BE06BD /* FinderOpen */ = { + isa = PBXNativeTarget; + buildConfigurationList = C781216F2BC892A000BE06BD /* Build configuration list for PBXNativeTarget "FinderOpen" */; + buildPhases = ( + C781215F2BC892A000BE06BD /* Sources */, + C78121602BC892A000BE06BD /* Frameworks */, + C78121612BC892A000BE06BD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FinderOpen; + productName = FinderOpen; + productReference = C78121632BC892A000BE06BD /* FinderOpen.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -307,7 +375,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1500; + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1510; TargetAttributes = { C728930E2AFD51EA00C8C1CD = { @@ -316,6 +384,9 @@ C77B8FFF2AF18E2E009CC655 = { CreatedOnToolsVersion = 15.0.1; }; + C78121622BC892A000BE06BD = { + CreatedOnToolsVersion = 15.3; + }; }; }; buildConfigurationList = C77B8FFB2AF18E2E009CC655 /* Build configuration list for PBXProject "Pearcleaner" */; @@ -336,6 +407,7 @@ targets = ( C77B8FFF2AF18E2E009CC655 /* Pearcleaner */, C728930E2AFD51EA00C8C1CD /* PearcleanerSentinel */, + C78121622BC892A000BE06BD /* FinderOpen */, ); }; /* End PBXProject section */ @@ -351,6 +423,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C78121612BC892A000BE06BD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -393,6 +472,7 @@ C7D31D4A2AFEB26700C7ED9E /* AppListItems.swift in Sources */, C77B901E2AF1A9DB009CC655 /* SettingsWindow.swift in Sources */, C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */, + C79D05202BC99EB40083F976 /* ThemeManager.swift in Sources */, C7D31D482AFEB23700C7ED9E /* TopBar.swift in Sources */, C7ACADE62B92A737000B5845 /* Features.swift in Sources */, C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */, @@ -403,8 +483,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C781215F2BC892A000BE06BD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C78121662BC892A000BE06BD /* FinderOpen.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + C781216A2BC892A000BE06BD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C78121622BC892A000BE06BD /* FinderOpen */; + targetProxy = C78121692BC892A000BE06BD /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ C72893142AFD51EA00C8C1CD /* Debug */ = { isa = XCBuildConfiguration; @@ -412,6 +508,7 @@ CODE_SIGN_ENTITLEMENTS = PearcleanerSentinel/PearcleanerSentinel.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = ""; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = BK8443AXLU; ENABLE_HARDENED_RUNTIME = YES; @@ -430,6 +527,7 @@ CODE_SIGN_ENTITLEMENTS = PearcleanerSentinel/PearcleanerSentinel.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = ""; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = BK8443AXLU; ENABLE_HARDENED_RUNTIME = YES; @@ -446,6 +544,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APP_BUILD = 34; + APP_VERSION = 3.5.0; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -477,6 +577,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = ""; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 33; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -499,6 +600,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 3.4.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -512,6 +614,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APP_BUILD = 34; + APP_VERSION = 3.5.0; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -543,6 +647,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = ""; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 33; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; @@ -559,6 +664,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 3.4.2; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -569,13 +675,14 @@ C77B90102AF18E2F009CC655 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Pearcleaner/Resources/Pearcleaner.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 33; + CURRENT_PROJECT_VERSION = "$(APP_BUILD)"; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -593,7 +700,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.4.2; + MARKETING_VERSION = "$(APP_VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -604,13 +711,14 @@ C77B90112AF18E2F009CC655 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Pearcleaner/Resources/Pearcleaner.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 33; + CURRENT_PROJECT_VERSION = "$(APP_BUILD)"; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; @@ -628,7 +736,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 3.4.2; + MARKETING_VERSION = "$(APP_VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -636,6 +744,66 @@ }; name = Release; }; + C781216D2BC892A000BE06BD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = FinderOpen/FinderOpen.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = "$(APP_BUILD)"; + DEVELOPMENT_TEAM = BK8443AXLU; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = FinderOpen/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = FinderOpen; + INFOPLIST_KEY_LSApplicationCategoryType = ""; + INFOPLIST_KEY_LSUIElement = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = "$(APP_VERSION)"; + PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner.FinderOpen; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + C781216E2BC892A000BE06BD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = FinderOpen/FinderOpen.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = "$(APP_BUILD)"; + DEVELOPMENT_TEAM = BK8443AXLU; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = FinderOpen/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = FinderOpen; + INFOPLIST_KEY_LSApplicationCategoryType = ""; + INFOPLIST_KEY_LSUIElement = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = "$(APP_VERSION)"; + PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner.FinderOpen; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -666,6 +834,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C781216F2BC892A000BE06BD /* Build configuration list for PBXNativeTarget "FinderOpen" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C781216D2BC892A000BE06BD /* Debug */, + C781216E2BC892A000BE06BD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ diff --git a/Pearcleaner.xcodeproj/xcshareddata/xcschemes/FinderOpen.xcscheme b/Pearcleaner.xcodeproj/xcshareddata/xcschemes/FinderOpen.xcscheme new file mode 100644 index 0000000..fc80125 --- /dev/null +++ b/Pearcleaner.xcodeproj/xcshareddata/xcschemes/FinderOpen.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pearcleaner/Logic/AppCommands.swift b/Pearcleaner/Logic/AppCommands.swift index 711c122..c67190f 100644 --- a/Pearcleaner/Logic/AppCommands.swift +++ b/Pearcleaner/Logic/AppCommands.swift @@ -41,15 +41,16 @@ struct AppCommands: Commands { let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState) DispatchQueue.main.asyncAfter(deadline: .now() + 1) { appState.sortedApps = [] -// appState.sortedApps.systemApps = [] appState.sortedApps = sortedApps -// appState.sortedApps.systemApps = sortedApps.systemApps if instantSearch { Task(priority: .high){ - loadAllPaths(allApps: sortedApps, appState: appState, locations: locations) + loadAllPaths(allApps: sortedApps, appState: appState, locations: locations) { + appState.reload.toggle() + } } + } else { + appState.reload.toggle() } - appState.reload.toggle() } } } label: { diff --git a/Pearcleaner/Logic/AppState.swift b/Pearcleaner/Logic/AppState.swift index c433945..c427656 100644 --- a/Pearcleaner/Logic/AppState.swift +++ b/Pearcleaner/Logic/AppState.swift @@ -7,6 +7,7 @@ import Foundation import SwiftUI +import FinderSync let home = FileManager.default.homeDirectoryForCurrentUser.path @@ -31,6 +32,7 @@ class AppState: ObservableObject @Published var popCount: Int = 0 @Published var instantProgress: Double = 0.0 @Published var instantTotal: Double = 0.0 + @Published var finderExtensionEnabled: Bool = false init() { @@ -54,6 +56,21 @@ class AppState: ObservableObject fileSize: [:], fileIcon: [:] ) + updateExtensionStatus() + NotificationCenter.default.addObserver( + self, + selector: #selector(updateExtensionStatus), + name: NSApplication.didBecomeActiveNotification, + object: nil + ) + + } + + @objc func updateExtensionStatus() { + let extentionStatus = FIFinderSyncController.isExtensionEnabled + DispatchQueue.main.async { + self.finderExtensionEnabled = extentionStatus + } } } diff --git a/Pearcleaner/Logic/DeepLink.swift b/Pearcleaner/Logic/DeepLink.swift index a4539ec..74e10e1 100644 --- a/Pearcleaner/Logic/DeepLink.swift +++ b/Pearcleaner/Logic/DeepLink.swift @@ -26,7 +26,6 @@ class DeeplinkManager { // This handles dropping an app onto Pearcleaner if url.pathExtension == "app" { handleAppBundle(url: url, appState: appState, locations: locations) - // This handles sentinel monitor launch } else if url.scheme == DeepLinkConstants.scheme, url.host == DeepLinkConstants.host, let components = URLComponents(url: url, resolvingAgainstBaseURL: true), diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index 63d34c1..41bbf09 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -11,15 +11,17 @@ import SwiftUI struct SimpleButtonStyle: ButtonStyle { @State private var hovered = false let icon: String - let label: String? + let label: String let help: String let color: Color + let size: CGFloat - init(icon: String, label: String? = "", help: String, color: Color) { + init(icon: String, label: String = "", help: String, color: Color = Color("mode"), size: CGFloat = 20) { self.icon = icon self.label = label self.help = help self.color = color + self.size = size } func makeBody(configuration: Self.Configuration) -> some View { @@ -27,8 +29,8 @@ struct SimpleButtonStyle: ButtonStyle { Image(systemName: icon) .resizable() .scaledToFit() - .frame(width: 20) - if let label = label, !label.isEmpty { + .frame(width: size, height: size) + if !label.isEmpty { Text(label) } } @@ -45,34 +47,6 @@ struct SimpleButtonStyle: ButtonStyle { } -struct NavButtonBottomBarStyle: ButtonStyle { - @State private var isHovered = false - var image: String - var help: String - - func makeBody(configuration: Configuration) -> some View { - ZStack { - Image(systemName: image) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - .foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5)) - Rectangle() - .foregroundColor(.clear) - .frame(width: 36, height: 36) - .cornerRadius(6) - .contentShape(Rectangle()) - } - .scaleEffect(configuration.isPressed ? 0.95 : 1) - .buttonStyle(.plain) - .onHover { inside in - isHovered = inside - } - .help(help) - } -} - - struct InfoButton: View { @State private var isPopoverPresented: Bool = false @@ -215,7 +189,7 @@ public struct NewFeatureView: View { showFeature = false } } - .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close", color: .gray)) + .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close", color: (Color("mode").opacity(0.5)))) .onHover { isHovered in if isHovered { NSCursor.pointingHand.push() @@ -249,43 +223,36 @@ public struct NewFeatureView: View { public struct SlideableDivider: View { + @EnvironmentObject private var themeSettings: ThemeSettings @Binding var dimension: Double @State private var dimensionStart: Double? - @State private var handleWidth: Double = 2 + @State private var handleWidth: Double = 4 @State private var handleHeight: Double = 30 + @State private var isHovered: Bool = false public init(dimension: Binding) { self._dimension = dimension } public var body: some View { Rectangle() - .foregroundStyle(Color.gray.opacity(0.3)) - .frame(width: 1) - .overlay( - VStack(spacing: 0) { - Spacer() - RoundedRectangle(cornerRadius: 50) - .fill(Color("mode").opacity(0.3)) - .frame(width: handleWidth, height: handleHeight) - .onHover { inside in - if inside { - NSCursor.resizeLeftRight.push() + .foregroundStyle(Color("mode").opacity(0.0)) + .frame(width: 10) + .onHover { inside in + if inside { + NSCursor.resizeLeftRight.push() - } else { - NSCursor.pop() + } else { + NSCursor.pop() - } - } - .contextMenu { - Button("Reset Size") { - dimension = 280 - } - } - .gesture(drag) - Spacer() } - .offset(x: 5) - ) + } + .contextMenu { + Button("Reset Size") { + dimension = 280 + } + } + .gesture(drag) + .help("Right click to reset size") } var drag: some Gesture { @@ -302,13 +269,13 @@ public struct SlideableDivider: View { let maxWidth: Double = 350 dimension = max(minWidth, min(maxWidth, newDimension)) NSCursor.closedHand.set() - handleWidth = 4 + handleWidth = 6 handleHeight = 40 } .onEnded { val in dimensionStart = nil NSCursor.arrow.set() - handleWidth = 2 + handleWidth = 4 handleHeight = 30 } } @@ -323,18 +290,18 @@ struct LabeledDivider: View { HStack(spacing: 0) { Rectangle() .frame(height: 1) - .foregroundColor(.gray.opacity(0.2)) + .foregroundColor(Color("mode").opacity(0.2)) Text(label) .textCase(.uppercase) .font(.title2) - .foregroundColor(.gray.opacity(0.6)) + .foregroundColor(Color("mode").opacity(0.6)) .padding(.horizontal, 10) .frame(minWidth: 80) Rectangle() .frame(height: 1) - .foregroundColor(.gray.opacity(0.2)) + .foregroundColor(Color("mode").opacity(0.2)) } .frame(minHeight: 35) } @@ -497,7 +464,7 @@ struct SimpleSearchStyle: TextFieldStyle { } RoundedRectangle(cornerRadius: 6) - .strokeBorder(Color.gray.opacity(0.2), lineWidth: 1) + .strokeBorder(Color("mode").opacity(0.2), lineWidth: 1) .allowsHitTesting(false) } } @@ -767,7 +734,7 @@ struct PillPicker: View { Text(i == 0 ? "Apps" : (i == 1 ? "Widgets" : "Plugins")) .font(.system(size: 12)) - .foregroundColor(index == i ? (colorScheme == .dark ? .black : Color("AccentColor")) : .gray) + .foregroundColor(index == i ? (colorScheme == .dark ? .black : Color("AccentColor")) : Color("mode")) } .padding(5) .frame(height: 25) @@ -826,3 +793,47 @@ struct PearDropView: View { } } + + +// Used for testing UI bounds +extension View { + func bounds() -> some View { + self.border(Color.red, width: 1) + } +} + + +@ViewBuilder +func backgroundView(themeSettings: ThemeSettings, darker: Bool = false, glass: Bool = false) -> some View { + if glass { + GlassEffect(material: .sidebar, blendingMode: .behindWindow) + .edgesIgnoringSafeArea(.all) + } else { + darker ? themeSettings.themeColor.darker(by: 5).edgesIgnoringSafeArea(.all) : themeSettings.themeColor.edgesIgnoringSafeArea(.all) + } +} + + + +struct PresetColor: ButtonStyle { + var fillColor: Color + var label: String + + func makeBody(configuration: Configuration) -> some View { + HStack(alignment: .center, spacing: 10) { + Circle() + .frame(width: 20, height: 20) + .fixedSize(horizontal: true, vertical: true) + .foregroundColor(fillColor) + .background(fillColor) + .clipShape(Circle()) + .help(label) + .overlay( + RoundedRectangle(cornerRadius: 25) + .strokeBorder(Color("mode").opacity(0.8), lineWidth: 1.5)) +// Text(label) + } + .onHover { inside in inside ? NSCursor.pointingHand.push() : NSCursor.pop() } + + } +} diff --git a/Pearcleaner/Logic/ThemeManager.swift b/Pearcleaner/Logic/ThemeManager.swift new file mode 100644 index 0000000..110092a --- /dev/null +++ b/Pearcleaner/Logic/ThemeManager.swift @@ -0,0 +1,59 @@ +// +// ThemeManager.swift +// Pearcleaner +// +// Created by Alin Lupascu on 4/12/24. +// + +import Foundation +import SwiftUI +import AppKit + +class ThemeSettings: ObservableObject { + static let shared = ThemeSettings() + private let userDefaults = UserDefaults.standard + private let colorKey = "themeColor" + + @Published var themeColor: Color { + didSet { + saveThemeColor() + } + } + + // light - Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1) + // dark - Color(.sRGB, red: 0.149, green: 0.149, blue: 0.149, opacity: 1) + + init() { + // Initialize color from UserDefaults or use a default value + if let components = UserDefaults.standard.object(forKey: colorKey) as? [CGFloat], components.count >= 4 { + themeColor = Color(.sRGB, red: components[0], green: components[1], blue: components[2], opacity: components[3]) + } else { + themeColor = Color.clear + } + } + + func saveThemeColor() { + let nsColor = NSColor(themeColor) + if let components = nsColor.cgColor.components { + UserDefaults.standard.set(components, forKey: colorKey) + } + } + + func resetToDefault(dark: Bool = true) { + themeColor = dark ? Color(.sRGB, red: 0.149, green: 0.149, blue: 0.149, opacity: 1) : Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1) + saveThemeColor() + } +} + +extension Color { + func darker(by percentage: CGFloat = 30.0) -> Color { + var hsb = (hue: CGFloat(0), saturation: CGFloat(0), brightness: CGFloat(0), alpha: CGFloat(0)) + NSColor(self).getHue(&hsb.hue, saturation: &hsb.saturation, brightness: &hsb.brightness, alpha: &hsb.alpha) + return Color(hue: hsb.hue, saturation: hsb.saturation, brightness: max(hsb.brightness - percentage/100, 0), opacity: hsb.alpha) + } + func lighter(by percentage: CGFloat = 30.0) -> Color { + var hsb = (hue: CGFloat(0), saturation: CGFloat(0), brightness: CGFloat(0), alpha: CGFloat(0)) + NSColor(self).getHue(&hsb.hue, saturation: &hsb.saturation, brightness: &hsb.brightness, alpha: &hsb.alpha) + return Color(hue: hsb.hue, saturation: hsb.saturation, brightness: min(hsb.brightness + percentage / 100, 1.0), opacity: hsb.alpha) + } +} diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index bb107fe..62f734b 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -59,6 +59,7 @@ struct PearcleanerApp: App { .environmentObject(appState) .environmentObject(locations) .environmentObject(fsm) + .environmentObject(ThemeSettings.shared) .preferredColorScheme(displayMode.colorScheme) .handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*")) .onOpenURL(perform: { url in @@ -114,6 +115,7 @@ struct PearcleanerApp: App { .environmentObject(locations) .environmentObject(appState) .environmentObject(fsm) + .environmentObject(ThemeSettings.shared) .preferredColorScheme(displayMode.colorScheme) }, icon: selectedMenubarIcon) } @@ -170,6 +172,7 @@ struct PearcleanerApp: App { .environmentObject(appState) .environmentObject(locations) .environmentObject(fsm) + .environmentObject(ThemeSettings.shared) .toolbarBackground(.clear) .preferredColorScheme(displayMode.colorScheme) } @@ -192,6 +195,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { findAndHideWindows(named: ["Pearcleaner"]) NSApplication.shared.setActivationPolicy(.accessory) } + } diff --git a/Pearcleaner/Resources/Info.plist b/Pearcleaner/Resources/Info.plist index 16c1874..c1c7a18 100644 --- a/Pearcleaner/Resources/Info.plist +++ b/Pearcleaner/Resources/Info.plist @@ -2,6 +2,10 @@ + CFBundleShortVersionString + $(APP_VERSION) + CFBundleVersion + $(APP_BUILD) CFBundleDocumentTypes diff --git a/Pearcleaner/Settings/About.swift b/Pearcleaner/Settings/About.swift index 744adc7..92b9eea 100644 --- a/Pearcleaner/Settings/About.swift +++ b/Pearcleaner/Settings/About.swift @@ -53,14 +53,14 @@ struct AboutSettingsTab: View { Text("Microsoft Designer") Text("Application icon resource") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } Spacer() Button(""){ NSWorkspace.shared.open(URL(string: "https://designer.microsoft.com/image-creator")!) } - .buttonStyle(SimpleButtonStyle(icon: "link", help: "View", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "link", help: "View")) } .padding(5) @@ -78,14 +78,14 @@ struct AboutSettingsTab: View { Text("Namelix") Text("Logo and branding generation") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } Spacer() Button(""){ NSWorkspace.shared.open(URL(string: "https://namelix.com/")!) } - .buttonStyle(SimpleButtonStyle(icon: "link", help: "View", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "link", help: "View")) } .padding(5) @@ -103,7 +103,7 @@ struct AboutSettingsTab: View { Text("Privacy Guides") Text("Inspired by open-source appcleaner script from Sun Knudsen") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) .lineLimit(2) } @@ -112,7 +112,7 @@ struct AboutSettingsTab: View { Button("") { NSWorkspace.shared.open(URL(string: "https://sunknudsen.com/privacy-guides/how-to-clean-uninstall-macos-apps-using-appcleaner-open-source-alternative")!) } - .buttonStyle(SimpleButtonStyle(icon: "link", help: "View", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "link", help: "View")) } .padding(5) @@ -130,14 +130,14 @@ struct AboutSettingsTab: View { Text("AppCleaner") Text("Inspired by AppCleaner from Freemacsoft") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } Spacer() Button(""){ NSWorkspace.shared.open(URL(string: "https://freemacsoft.net/appcleaner/")!) } - .buttonStyle(SimpleButtonStyle(icon: "link", help: "View", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "link", help: "View")) } .padding(5) diff --git a/Pearcleaner/Settings/General.swift b/Pearcleaner/Settings/General.swift index 47cc871..df9676f 100644 --- a/Pearcleaner/Settings/General.swift +++ b/Pearcleaner/Settings/General.swift @@ -8,6 +8,7 @@ import Foundation import SwiftUI import ServiceManagement +import FinderSync struct GeneralSettingsTab: View { @EnvironmentObject var appState: AppState @@ -25,7 +26,7 @@ struct GeneralSettingsTab: View { @AppStorage("settings.general.instant") private var instantSearch: Bool = true @AppStorage("settings.general.selectedTheme") var selectedTheme: String = "Auto" @AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true - +// @State private var finderExtensionEnabled: Bool = false @State private var diskStatus: Bool = false @State private var accessStatus: Bool = false private let themes = ["Auto", "Dark", "Light"] @@ -49,11 +50,11 @@ struct GeneralSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("\(instantSearch ? "Instant search is enabled" : "Instant search is disabled")") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } InfoButton(text: "When instant search is enabled, all application files are gathered and cached for later use on startup instead of on each app click. There might be a slight delay of a few seconds when launching Pearcleaner depending on the amount of apps you have installed.", color: nil, label: "") @@ -73,11 +74,11 @@ struct GeneralSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("\(brew ? "Homebrew cleanup is enabled" : "Homebrew cleanup is disabled")") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } InfoButton(text: "When homebrew cleanup is enabled, Pearcleaner will check if the app you are removing was installed via homebrew and execute a brew uninstall and brew cleanup command as well to let homebrew know that the app is removed. This way your homebrew list will be synced up correctly and caching will be removed.\n\nNOTE: If you undo the file delete with CMD+Z, the files will be put back but homebrew will not be aware of it. To get the homebrew list back in sync you'd need to run:\n brew install APPNAME --force", color: nil, label: "") @@ -97,11 +98,11 @@ struct GeneralSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("File list sorting mode") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } InfoButton(text: "When searching for app files or leftover files, the list will be sorted based on this choice", color: nil, label: "") Spacer() @@ -114,28 +115,6 @@ struct GeneralSettingsTab: View { } .padding(5) .padding(.leading) -// HStack(spacing: 0) { -// Image(systemName: selectedSortAlpha ? "textformat.abc" : "textformat.123") -// .resizable() -// .scaledToFit() -// .frame(width: 20, height: 20) -// .padding(.trailing) -// .foregroundStyle(.gray) -// VStack(alignment: .leading, spacing: 5) { -// Text("\(selectedSortAlpha ? "File list sorted alphabetically" : "File list sorted by size")") -// .font(.callout) -// .foregroundStyle(.gray) -// } -// -// InfoButton(text: "When searching for app files or leftover files, the list will be sorted based on this choice", color: nil, label: "") -// -// Spacer() -// Toggle(isOn: $selectedSortAlpha, label: { -// }) -// .toggleStyle(.switch) -// } -// .padding(5) -// .padding(.leading) // === Perms ================================================================================================ @@ -163,7 +142,7 @@ struct GeneralSettingsTab: View { .saturation(displayMode.colorScheme == .dark ? 0.5 : 1) Text(diskStatus ? "Full Disk permission granted" : "Full Disk permission **NOT** granted") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) Spacer() Button("") { @@ -171,7 +150,7 @@ struct GeneralSettingsTab: View { NSWorkspace.shared.open(url) } } - .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View disk permissions pane", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View disk permissions pane")) } .padding(5) @@ -188,7 +167,7 @@ struct GeneralSettingsTab: View { .saturation(displayMode.colorScheme == .dark ? 0.5 : 1) Text(accessStatus ? "Accessibility permission granted" : "Accessibility permission **NOT** granted") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) Spacer() Button("") { @@ -196,7 +175,7 @@ struct GeneralSettingsTab: View { NSWorkspace.shared.open(url) } } - .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View accessibility permissions pane", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "folder", help: "View accessibility permissions pane")) } .padding(5) @@ -224,7 +203,7 @@ struct GeneralSettingsTab: View { .saturation(displayMode.colorScheme == .dark ? 0.5 : 1) Text(sentinel ? "Detecting when apps are moved to Trash" : "**NOT** detecting when apps are moved to Trash") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) Spacer() Toggle(isOn: $sentinel, label: { @@ -244,6 +223,44 @@ struct GeneralSettingsTab: View { + // === Finder Extension ============================================================================================= + + Divider() + .padding() + + HStack() { + Text("Finder Extension").font(.title2) + Spacer() + } + .padding(.leading) + + HStack(spacing: 0) { + Image(systemName: appState.finderExtensionEnabled ? "puzzlepiece.extension.fill" : "puzzlepiece.extension") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .padding(.trailing) + .foregroundStyle(appState.finderExtensionEnabled ? .green : .red) + Text(appState.finderExtensionEnabled ? "Context menu extension for Finder is enabled" : "Context menu extension for Finder is disabled") + .font(.callout) + .foregroundStyle(Color("mode").opacity(0.5)) + InfoButton(text: "Enabling the extension will allow you to right click apps in Finder and quickly uninstall them with Pearcleaner", color: nil, label: "") + + Spacer() + + Button("Extensions") { + FIFinderSyncController.showExtensionManagementInterface() + } + .buttonStyle(SimpleButtonStyle(icon: "folder", help: "Show Extensions Pane")) + + + } + .padding(5) + .padding(.leading) + + + + @@ -253,11 +270,12 @@ struct GeneralSettingsTab: View { .onAppear { diskStatus = checkAndRequestFullDiskAccess(appState: appState, skipAlert: true) accessStatus = checkAndRequestAccessibilityAccess(appState: appState) + appState.updateExtensionStatus() } } .padding(20) - .frame(width: 500, height: 460) + .frame(width: 500, height: 560) } diff --git a/Pearcleaner/Settings/Interface.swift b/Pearcleaner/Settings/Interface.swift index 0830cf8..eeee774 100644 --- a/Pearcleaner/Settings/Interface.swift +++ b/Pearcleaner/Settings/Interface.swift @@ -14,7 +14,9 @@ struct InterfaceSettingsTab: View { @EnvironmentObject var appState: AppState @EnvironmentObject var locations: Locations @EnvironmentObject var fsm: FolderSettingsManager + @EnvironmentObject var themeSettings: ThemeSettings @State private var windowSettings = WindowSettings() + @Environment(\.colorScheme) var colorScheme: ColorScheme @AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("displayMode") var displayMode: DisplayMode = .system @@ -31,7 +33,16 @@ struct InterfaceSettingsTab: View { let icons = ["externaldrive", "trash", "folder", "pear-1", "pear-1.5", "pear-2", "pear-3", "pear-4"] private let themes = ["Auto", "Dark", "Light"] +// @State private var slate = Color(.sRGB, red: 0.188143, green: 0.208556, blue: 0.262679, opacity: 1) +// @State private var solarized = Color(.sRGB, red: 0.117257, green: 0.22506, blue: 0.249171, opacity: 1) +// @State private var dracula = Color(.sRGB, red: 0.268614, green: 0.264737, blue: 0.383503, opacity: 1) + var body: some View { + + let slate = displayMode.colorScheme == .light ? Color(.sRGB, red: 0.499549, green: 0.545169, blue: 0.682028, opacity: 1) : Color(.sRGB, red: 0.188143, green: 0.208556, blue: 0.262679, opacity: 1) + let solarized = displayMode.colorScheme == .light ? Color(.sRGB, red: 0.554372, green: 0.6557, blue: 0.734336, opacity: 1) : Color(.sRGB, red: 0.117257, green: 0.22506, blue: 0.249171, opacity: 1) + let dracula = displayMode.colorScheme == .light ? Color(.sRGB, red: 0.567094, green: 0.562125, blue: 0.81285, opacity: 1) : Color(.sRGB, red: 0.268614, green: 0.264737, blue: 0.383503, opacity: 1) + Form { VStack { @@ -47,11 +58,11 @@ struct InterfaceSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("\(glass ? "Transparent material enabled" : "Transparent material disabled")") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } Spacer() Toggle(isOn: $glass, label: { @@ -65,6 +76,57 @@ struct InterfaceSettingsTab: View { .padding(.leading) + HStack(spacing: 0) { + Image(systemName: "paintbrush") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .padding(.trailing) + .foregroundStyle(Color("mode").opacity(0.5)) + VStack(alignment: .leading, spacing: 5) { + Text("Application base color") + .font(.callout) + .foregroundStyle(Color("mode").opacity(0.5)) + } + InfoButton(text: "When using a custom color, you might need to change the application color mode below to Dark or Light so text is readable", color: nil, label: "") + Spacer() + + Button("") { + themeSettings.themeColor = slate + themeSettings.saveThemeColor() + } + .buttonStyle(PresetColor(fillColor: slate, label: "Slate")) + + Button("") { + themeSettings.themeColor = dracula + themeSettings.saveThemeColor() + } + .buttonStyle(PresetColor(fillColor: dracula, label: "Dracula")) + .padding(.horizontal) + + Button("") { + themeSettings.themeColor = solarized + themeSettings.saveThemeColor() + } + .buttonStyle(PresetColor(fillColor: solarized, label: "Solarized")) + + Spacer() + + ColorPicker("", selection: $themeSettings.themeColor, supportsOpacity: false) + .onChange(of: themeSettings.themeColor) { newValue in + themeSettings.saveThemeColor() + } + .padding(.horizontal, 5) + Button("") { + themeSettings.resetToDefault(dark: colorScheme == .dark) + } + .buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Reset color to default")) + + } + .padding(5) + .padding(.leading) + + HStack(spacing: 0) { Image(systemName: { @@ -81,12 +143,13 @@ struct InterfaceSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("Application color mode") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } + InfoButton(text: "Changing the color mode will reset the base color to defaults", color: nil, label: "") Spacer() Picker("", selection: $selectedTheme) { ForEach(themes, id: \.self) { theme in @@ -99,6 +162,7 @@ struct InterfaceSettingsTab: View { switch newTheme { case "Auto": displayMode.colorScheme = nil + themeSettings.resetToDefault(dark: isDarkModeEnabled()) // Refresh foreground colors DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.selectedTab = .interface @@ -108,11 +172,19 @@ struct InterfaceSettingsTab: View { } case "Dark": displayMode.colorScheme = .dark + themeSettings.resetToDefault(dark: true) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.selectedTab = .interface + } if menubarEnabled{ MenuBarExtraManager.shared.restartMenuBarExtra() } case "Light": displayMode.colorScheme = .light + themeSettings.resetToDefault(dark: false) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.selectedTab = .interface + } if menubarEnabled{ MenuBarExtraManager.shared.restartMenuBarExtra() } @@ -145,11 +217,11 @@ struct InterfaceSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("\(mini ? "Mini window mode" : "Full size window mode")") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } Spacer() Toggle(isOn: $mini, label: { @@ -167,6 +239,7 @@ struct InterfaceSettingsTab: View { .environmentObject(locations) .environmentObject(appState) .environmentObject(fsm) + .environmentObject(ThemeSettings.shared) .preferredColorScheme(displayMode.colorScheme) } updateOnMain(after: 0.1, { @@ -184,6 +257,7 @@ struct InterfaceSettingsTab: View { .environmentObject(locations) .environmentObject(appState) .environmentObject(fsm) + .environmentObject(ThemeSettings.shared) .preferredColorScheme(displayMode.colorScheme) } updateOnMain(after: 0.1, { @@ -204,11 +278,11 @@ struct InterfaceSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("\(miniView ? "Show apps list on startup" : "Show drop target on startup")") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.", color: nil, label: "") @@ -231,11 +305,11 @@ struct InterfaceSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("\(popoverStay ? "Popover window will stay on top" : "Popover window will not stay on top")") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the popover.", color: nil, label: "") @@ -267,11 +341,11 @@ struct InterfaceSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("\(menubarEnabled ? "Menubar icon enabled" : "Menubar icon disabled")") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } InfoButton(text: "When menubar icon is enabled, the main app window and dock icon will be disabled since the app will be put in accessory mode.", color: nil, label: "") Spacer() @@ -285,6 +359,7 @@ struct InterfaceSettingsTab: View { .environmentObject(locations) .environmentObject(appState) .environmentObject(fsm) + .environmentObject(ThemeSettings.shared) .preferredColorScheme(displayMode.colorScheme) }, icon: selectedMenubarIcon) NSApplication.shared.setActivationPolicy(.accessory) @@ -300,6 +375,7 @@ struct InterfaceSettingsTab: View { .environmentObject(locations) .environmentObject(appState) .environmentObject(fsm) + .environmentObject(ThemeSettings.shared) .preferredColorScheme(displayMode.colorScheme) } resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") @@ -309,6 +385,7 @@ struct InterfaceSettingsTab: View { .environmentObject(locations) .environmentObject(appState) .environmentObject(fsm) + .environmentObject(ThemeSettings.shared) .preferredColorScheme(displayMode.colorScheme) } resizeWindowAuto(windowSettings: windowSettings, title: "Pearcleaner") @@ -329,11 +406,11 @@ struct InterfaceSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("\(isLaunchAtLoginEnabled ? "Launch at login enabled" : "Launch at login disabled")") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } InfoButton(text: "This setting will affect Pearcleaner whether you're running in menubar mode or regular mode. If you disable menubar icon, you might want to disable this as well so Pearcleaner doesn't start on login.", color: nil, label: "") Spacer() @@ -370,11 +447,11 @@ struct InterfaceSettingsTab: View { .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { Text("Menubar icon") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } Spacer() Picker("", selection: $selectedMenubarIcon) { @@ -403,44 +480,12 @@ struct InterfaceSettingsTab: View { .padding(5) .padding(.leading) -// HStack(spacing: 0) { -// Image(systemName: "dock.rectangle") -// .resizable() -// .scaledToFit() -// .frame(width: 20, height: 20) -// .padding(.trailing) -// .foregroundStyle(.gray) -// VStack(alignment: .leading, spacing: 5) { -// Text("\(dockEnabled ? "Show dock icon" : "Hide dock icon")") -// .font(.callout) -// .foregroundStyle(.gray) -// } -// InfoButton(text: "This setting only affects Pearcleaner when the menubar icon is enabled, otherwise the dock icon will always show", color: nil, label: "") -// Spacer() -// Toggle(isOn: $dockEnabled, label: { -// }) -// .toggleStyle(.switch) -// .onChange(of: dockEnabled) { newValue in -// if newValue { -// NSApplication.shared.setActivationPolicy(.regular) -// } else { -// if menubarEnabled { -// NSApplication.shared.setActivationPolicy(.accessory) -// } -// -// } -// } -// -// } -// .padding(5) -// .padding(.leading) - Spacer() } } .padding(20) - .frame(width: 500, height: 520) + .frame(width: 500, height: 550) } diff --git a/Pearcleaner/Settings/SettingsWindow.swift b/Pearcleaner/Settings/SettingsWindow.swift index edd7a4b..27cb4cb 100644 --- a/Pearcleaner/Settings/SettingsWindow.swift +++ b/Pearcleaner/Settings/SettingsWindow.swift @@ -10,6 +10,7 @@ import SwiftUI struct SettingsView: View { @EnvironmentObject var appState: AppState @EnvironmentObject var fsm: FolderSettingsManager + @EnvironmentObject var themeSettings: ThemeSettings @Binding var showPopover: Bool @Binding var search: String @Binding var showFeature: Bool @@ -49,7 +50,8 @@ struct SettingsView: View { } .tag(CurrentTabView.about) } - .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) + .background(backgroundView(themeSettings: themeSettings, glass: glass)) +// .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) } diff --git a/Pearcleaner/Settings/Update.swift b/Pearcleaner/Settings/Update.swift index 8396e36..39debc1 100644 --- a/Pearcleaner/Settings/Update.swift +++ b/Pearcleaner/Settings/Update.swift @@ -66,7 +66,7 @@ struct UpdateSettingsTab: View { Text("Showing last 3 releases") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) @@ -76,28 +76,28 @@ struct UpdateSettingsTab: View { Button(""){ loadGithubReleases(appState: appState) } - .buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Reload release notes", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Reload release notes")) Spacer() Button(""){ showFeature.toggle() } - .buttonStyle(SimpleButtonStyle(icon: "star", help: "Show last feature alert", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "star", help: "Show last feature alert")) Spacer() Button(""){ loadGithubReleases(appState: appState, manual: true) } - .buttonStyle(SimpleButtonStyle(icon: "arrow.down.square", help: "Check for updates", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "arrow.down.square", help: "Check for updates")) Spacer() Button(""){ NSWorkspace.shared.open(URL(string: "https://github.com/alienator88/Pearcleaner/releases")!) } - .buttonStyle(SimpleButtonStyle(icon: "link", help: "View releases on GitHub", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "link", help: "View releases on GitHub")) Spacer() } diff --git a/Pearcleaner/Views/AppListItems.swift b/Pearcleaner/Views/AppListItems.swift index 617d3b2..4de3854 100644 --- a/Pearcleaner/Views/AppListItems.swift +++ b/Pearcleaner/Views/AppListItems.swift @@ -10,13 +10,14 @@ import SwiftUI struct AppListItems: View { @EnvironmentObject var appState: AppState + @EnvironmentObject var themeSettings: ThemeSettings @Binding var search: String @State private var isHovered = false @State private var windowSettings = WindowSettings() @Environment(\.colorScheme) var colorScheme -// @AppStorage("settings.general.mini") private var mini: Bool = false -// @AppStorage("settings.general.popover") private var popoverStay: Bool = true @AppStorage("displayMode") var displayMode: DisplayMode = .system + @AppStorage("settings.general.miniview") private var miniView: Bool = true + @AppStorage("settings.general.mini") private var mini: Bool = false @Binding var showPopover: Bool @EnvironmentObject var locations: Locations let itemId = UUID() @@ -24,15 +25,12 @@ struct AppListItems: View { var isSelected: Bool { appState.appInfo.path == appInfo.path } - + @State private var hoveredItemPath: URL? = nil var body: some View { -// VStack(alignment: .leading, spacing: 5) { - - HStack(alignment: .center) { - if isHovered || isSelected { + if (isHovered || isSelected) && mini { RoundedRectangle(cornerRadius: 50) .fill(isSelected ? Color("pear") : Color("mode").opacity(0.5)) .frame(width: isSelected ? 4 : 2, height: 25) @@ -41,24 +39,12 @@ struct AppListItems: View { if let appIcon = appInfo.appIcon { ZStack { -// RoundedRectangle(cornerRadius: 8) -// .fill(Color(appIcon.averageColor!)) -// .frame(width: 35, height: 35) -// .saturation(3) -// .opacity(0.5) -//// .brightness(displayMode.colorScheme == .dark ? 0 : 0.5) -//// .shadow(color: .black, radius: 1, y: 2) -// RoundedRectangle(cornerRadius: 8) -// .strokeBorder(Color("mode").opacity(0.1), lineWidth: 1) -// .frame(width: 35, height: 35) Image(nsImage: appIcon) .resizable() .aspectRatio(contentMode: .fit) -// .scaledToFit() .frame(width: 30, height: 30) .clipShape(RoundedRectangle(cornerRadius: 8)) .shadow(color: .black.opacity(0.5), radius: 2, y: 2) -// .grayscale(opacityForItem()) } } @@ -100,31 +86,73 @@ struct AppListItems: View { .font(.footnote) .foregroundStyle(Color("mode").opacity(0.5)) + if (isHovered || isSelected) && !mini { + Triangle() + .fill(isSelected ? themeSettings.themeColor : Color("mode").opacity(0.1)) + .frame(width: isSelected ? 16 : 8, height: 30) + .padding(.leading, 5) + .offset(x: 22) + .zIndex(5) + } + } .padding(.horizontal, 5) .help(appInfo.appName) .onHover { hovering in withAnimation(Animation.easeIn(duration: 0.2)) { self.isHovered = hovering + self.hoveredItemPath = isHovered ? appInfo.path : nil } } .onTapGesture { withAnimation(Animation.easeInOut(duration: 0.4)) { - showAppInFiles(appInfo: appInfo, appState: appState, locations: locations, showPopover: $showPopover) + if isSelected { + appState.appInfo = .empty + appState.currentView = miniView ? .apps : .empty + showPopover = false + } else { + showAppInFiles(appInfo: appInfo, appState: appState, locations: locations, showPopover: $showPopover) + } } } +// .grayscale(opacityForItem(appInfo.path)) } - func opacityForItem() -> Double { + func opacityForItem(_ path: URL) -> Double { // Check if any item is selected let isAnyItemSelected = appState.sortedApps.contains(where: { $0.path == appState.appInfo.path }) - // If this item is selected or no items are selected, keep full opacity - // Otherwise, reduce opacity - return isAnyItemSelected ? (appState.appInfo.path == appInfo.path ? 0 : 1.0) : 0 + + let isItemSelected = appState.appInfo.path == path + let isItemHovered = hoveredItemPath == path + + // Logic to determine grayscale level + if isItemSelected || !isAnyItemSelected || isItemHovered { + return 0 // No grayscale + } else { + return 1.0 // Full grayscale + } + +// return isAnyItemSelected ? (appState.appInfo.path == appInfo.path ? 0 : 1.0) : 0 } } +struct Triangle: Shape { + func path(in rect: CGRect) -> Path { + var path = Path() + + // Start at the top right + path.move(to: CGPoint(x: rect.maxX, y: rect.minY)) + // Add line to the bottom right + path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) + // Add line to the left point + path.addLine(to: CGPoint(x: rect.minX, y: rect.midY)) + // Close the path + path.closeSubpath() + + return path + } +} diff --git a/Pearcleaner/Views/AppsListView.swift b/Pearcleaner/Views/AppsListView.swift index ecd9208..0c2a7d4 100644 --- a/Pearcleaner/Views/AppsListView.swift +++ b/Pearcleaner/Views/AppsListView.swift @@ -11,6 +11,8 @@ import SwiftUI struct AppsListView: View { @Binding var search: String @Binding var showPopover: Bool + @AppStorage("settings.general.mini") private var mini: Bool = false + var filteredApps: [AppInfo] var body: some View { @@ -23,11 +25,13 @@ struct AppsListView: View { SectionView(title: "User", count: filteredUserApps.count, apps: filteredUserApps, search: $search, showPopover: $showPopover) } + if !filteredSystemApps.isEmpty { SectionView(title: "System", count: filteredSystemApps.count, apps: filteredSystemApps, search: $search, showPopover: $showPopover) } } .padding(.horizontal) + .padding(.top, !mini ? 4 : 0) } .scrollIndicators(.never) } diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index b57f8bb..0d810d3 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -38,23 +38,17 @@ struct FilesView: View { VStack { Spacer() Text("Searching the file system").font(.title3) - .foregroundStyle((.gray.opacity(0.8))) - - HStack { - ProgressView() - .progressViewStyle(.linear) - .frame(width: 400, height: 10) - Text("\(elapsedTime)") - .font(.caption) - .foregroundStyle((.gray.opacity(0.8))) - .opacity(elapsedTime == 0 ? 0 : 1) -// Image(systemName: "\(elapsedTime).circle") -// .resizable() -// .aspectRatio(contentMode: .fit) -// .frame(width: 16, height: 16) -// .foregroundStyle((.gray.opacity(0.8))) -// .opacity(elapsedTime == 0 ? 0 : 1) - } + .foregroundStyle((Color("mode").opacity(0.5))) + + ProgressView() + .progressViewStyle(.linear) + .frame(width: 400, height: 10) + + Text("\(elapsedTime)") + .font(.title).monospacedDigit() + .foregroundStyle((Color("mode").opacity(0.5))) + .opacity(elapsedTime == 0 ? 0 : 1) + .contentTransition(.numericText()) Spacer() } @@ -62,7 +56,9 @@ struct FilesView: View { .transition(.opacity) .onAppear { self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in - self.elapsedTime += 1 + withAnimation { + self.elapsedTime += 1 + } } } .onDisappear { @@ -89,7 +85,7 @@ struct FilesView: View { pathFinder.findPaths() } } - .buttonStyle(NavButtonBottomBarStyle(image: "arrow.counterclockwise.circle.fill", help: "Rescan files")) + .buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle.fill", help: "Rescan files")) } @@ -101,7 +97,7 @@ struct FilesView: View { showPopover = false } } - .buttonStyle(NavButtonBottomBarStyle(image: "x.circle.fill", help: "Close")) + .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close")) } .padding([.horizontal, .top], 5) } @@ -130,7 +126,7 @@ struct FilesView: View { VStack(alignment: .leading, spacing: 5){ HStack { - Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold) + Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold).lineLimit(1) Text("•").foregroundStyle(Color("AccentColor")) Text("\(appState.appInfo.appVersion)").font(.title3) if appState.appInfo.appName.count < 5 { @@ -141,7 +137,7 @@ struct FilesView: View { Text("\(appState.appInfo.bundleIdentifier)") .lineLimit(1) .font(.title3) - .foregroundStyle((.gray.opacity(0.8))) + .foregroundStyle((Color("mode").opacity(0.5))) } .padding(.leading) @@ -150,7 +146,7 @@ struct FilesView: View { VStack(alignment: .trailing, spacing: 5) { Text("\(formatByte(size: appState.appInfo.totalSize))").font(.title).fontWeight(.bold) Text("\(appState.appInfo.fileSize.count == 1 ? "\(appState.selectedItems.count) / \(appState.appInfo.fileSize.count) item" : "\(appState.selectedItems.count) / \(appState.appInfo.fileSize.count) items")") - .font(.callout).foregroundStyle((.gray.opacity(0.8))) + .font(.callout).foregroundStyle((Color("mode").opacity(0.5))) } } @@ -218,7 +214,7 @@ struct FilesView: View { selectedSortAlpha.toggle() // selectedOption = selectedOption == "Alpha" ? "Size" : "Alpha" } - .buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size")) } .padding() @@ -407,7 +403,7 @@ struct FileDetailsItem: View { Button("") { NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) } - .buttonStyle(SimpleButtonStyle(icon: "folder.fill", help: "Show in Finder", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "folder.fill", help: "Show in Finder")) } .background( diff --git a/Pearcleaner/Views/MenuBarMiniAppView.swift b/Pearcleaner/Views/MenuBarMiniAppView.swift index 4e42fd5..a08fad2 100644 --- a/Pearcleaner/Views/MenuBarMiniAppView.swift +++ b/Pearcleaner/Views/MenuBarMiniAppView.swift @@ -12,7 +12,7 @@ struct MenuBarMiniAppView: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject var appState: AppState @EnvironmentObject var locations: Locations -// @State private var windowSettings = WindowSettings() + @EnvironmentObject var themeSettings: ThemeSettings @State private var animateGradient: Bool = false @Binding var search: String @State private var showSys: Bool = true @@ -55,7 +55,8 @@ struct MenuBarMiniAppView: View { } else { VStack(alignment: .center) { - AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps).padding(0) + AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps).padding(.top, 8) + HStack(spacing: 10) { Button("Leftover Files") { @@ -84,7 +85,7 @@ struct MenuBarMiniAppView: View { } } - .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files")) .padding(.leading, 10) SearchBarMiniBottom(search: $search) @@ -94,7 +95,7 @@ struct MenuBarMiniAppView: View { self.showMenu.toggle() } .padding(.trailing, 10) - .buttonStyle(SimpleButtonStyle(icon: "ellipsis.circle", help: "More", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "ellipsis.circle", help: "More")) .popover(isPresented: $showMenu) { VStack(alignment: .leading) { @@ -102,34 +103,36 @@ struct MenuBarMiniAppView: View { SettingsLink{ Label("Settings", systemImage: "gear") } - .buttonStyle(SimpleButtonStyle(icon: "gear", label: "Settings", help: "Settings", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "gear", label: "Settings", help: "Settings")) } else { Button("Settings") { NSApp.sendAction(Selector(("showPreferencesWindow:")), to: NSApp.delegate, from: nil) showMenu = false } - .buttonStyle(SimpleButtonStyle(icon: "gear", label: "Settings", help: "Settings", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "gear", label: "Settings", help: "Settings")) } Button("Quit") { NSApp.terminate(nil) } - .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", label: "Quit Pearcleaner", help: "Quit Pearcleaner", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", label: "Quit Pearcleaner", help: "Quit Pearcleaner")) } .padding() - .background( - Group { - if glass { - GlassEffect(material: .sidebar, blendingMode: .behindWindow) - .edgesIgnoringSafeArea(.all) - } else { - Rectangle() - .fill(Color("pop")) - .padding(-80) - - } - } - ) + .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) + +// .background( +// Group { +// if glass { +// GlassEffect(material: .sidebar, blendingMode: .behindWindow) +// .edgesIgnoringSafeArea(.all) +// } else { +// Rectangle() +// .fill(Color("pop")) +// .padding(-80) +// +// } +// } +// ) } @@ -139,7 +142,7 @@ struct MenuBarMiniAppView: View { // .padding(.horizontal) } - .padding(.vertical, 5) + .padding(.vertical, 8) .padding(.bottom, 8) } @@ -148,19 +151,21 @@ struct MenuBarMiniAppView: View { } .frame(minWidth: 300, minHeight: 370) .edgesIgnoringSafeArea(.all) - .background( - Group { - if glass { - GlassEffect(material: .sidebar, blendingMode: .behindWindow) - .edgesIgnoringSafeArea(.all) - } else { - Rectangle() - .fill(Color("pop")) - .padding(-80) - - } - } - ) + .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) + +// .background( +// Group { +// if glass { +// GlassEffect(material: .sidebar, blendingMode: .behindWindow) +// .edgesIgnoringSafeArea(.all) +// } else { +// Rectangle() +// .fill(Color("pop")) +// .padding(-80) +// +// } +// } +// ) .transition(.opacity) .popover(isPresented: $showPopover, arrowEdge: .leading) { VStack { @@ -174,19 +179,21 @@ struct MenuBarMiniAppView: View { } .interactiveDismissDisabled(popoverStay) - .background( - Group { - if glass { - GlassEffect(material: .sidebar, blendingMode: .behindWindow) - .edgesIgnoringSafeArea(.all) - } else { - Rectangle() - .fill(Color("pop")) - .padding(-80) - - } - } - ) + .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) + +// .background( +// Group { +// if glass { +// GlassEffect(material: .sidebar, blendingMode: .behindWindow) +// .edgesIgnoringSafeArea(.all) +// } else { +// Rectangle() +// .fill(Color("pop")) +// .padding(-80) +// +// } +// } +// ) .frame(width: 650, height: 500) } diff --git a/Pearcleaner/Views/MiniMode.swift b/Pearcleaner/Views/MiniMode.swift index 06b1d07..4e8ef71 100644 --- a/Pearcleaner/Views/MiniMode.swift +++ b/Pearcleaner/Views/MiniMode.swift @@ -11,6 +11,7 @@ import SwiftUI struct MiniMode: View { @EnvironmentObject var appState: AppState + @EnvironmentObject var themeSettings: ThemeSettings @Binding var search: String @State private var showSys: Bool = true @State private var showUsr: Bool = true @@ -49,20 +50,21 @@ struct MiniMode: View { } .interactiveDismissDisabled(popoverStay) - .background( - Group { - if glass { - GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) - } else { - Rectangle() - .fill(Color("pop")) - .padding(-80) - } - } -// Rectangle() -// .fill(Color("pop")) -// .padding(-80) - ) +// .background(Color("pop")) +// .background(backgroundView(themeSettings: themeSettings, glass: false).padding(-80)) + .background(backgroundView(themeSettings: themeSettings, glass: glass).padding(-80)) +// .background( +// Group { +// if glass { +// backgroundView(themeSettings: themeSettings).padding(-80) +//// GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) +// } else { +// Rectangle() +// .fill(Color("pop")) +// .padding(-80) +// } +// } +// ) .frame(width: 650, height: 550) } @@ -71,7 +73,8 @@ struct MiniMode: View { } .frame(minWidth: 300, minHeight: 345) .edgesIgnoringSafeArea(.all) - .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) + .background(backgroundView(themeSettings: themeSettings, glass: glass)) +// .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) // MARK: Background for whole app // .background(Color("bg").opacity(1)) // .background(VisualEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all)) @@ -93,22 +96,20 @@ struct MiniEmptyView: View { @Binding var showPopover: Bool var body: some View { - VStack() { - + VStack(alignment: .center) { + Spacer() - ZStack { - LinearGradient(gradient: Gradient(colors: [.green, .orange]), startPoint: .leading, endPoint: .trailing) - .mask( - Image(systemName: "plus.square.dashed") - .resizable() - .scaledToFit() - .frame(width: 120, height: 120) - .padding() - .fontWeight(.ultraLight) - ) - .frame(width: 160) - } + LinearGradient(gradient: Gradient(colors: [.green, .orange]), startPoint: .leading, endPoint: .trailing) + .mask( + Image(systemName: "plus.square.dashed") + .resizable() + .scaledToFit() + .frame(width: 120, height: 120, alignment: .center) + .padding() + .fontWeight(.ultraLight) + .offset(x: 5, y: 5) + ) Text("Drop your app here") .font(.title3) @@ -133,7 +134,6 @@ struct MiniEmptyView: View { } } } - Spacer() } Spacer() diff --git a/Pearcleaner/Views/RegularMode.swift b/Pearcleaner/Views/RegularMode.swift index 25cac1a..58e7944 100644 --- a/Pearcleaner/Views/RegularMode.swift +++ b/Pearcleaner/Views/RegularMode.swift @@ -10,6 +10,7 @@ import SwiftUI struct RegularMode: View { @EnvironmentObject var appState: AppState + @EnvironmentObject var themeSettings: ThemeSettings @AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280 @AppStorage("settings.general.instant") private var instantSearch: Bool = true @@ -69,21 +70,16 @@ struct RegularMode: View { } .frame(width: sidebarWidth) .padding(.vertical) -// .onHover { hovering in -// withAnimation(Animation.easeInOut(duration: 0.4)) { -// isHovering = hovering -// } -// } -// .opacity(!isHovering ? 1 : 0.5) - } } - .background(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil) + .background(backgroundView(themeSettings: themeSettings, darker: true, glass: glass)) .transition(.opacity) SlideableDivider(dimension: $sidebarWidth) + .background(backgroundView(themeSettings: themeSettings)) + .zIndex(3) // Details View @@ -104,30 +100,11 @@ struct RegularMode: View { } .transition(.opacity) } -// .background(slate) -// .onHover { hovering in -// withAnimation(Animation.easeInOut(duration: 0.4)) { -// isHovering = hovering -// } -// } -// .opacity((isHovering2 || (!isHovering && !isHovering2)) ? 1 : 0.5) - - + .zIndex(2) + .background(backgroundView(themeSettings: themeSettings)) } .frame(minWidth: 900, minHeight: 600) .edgesIgnoringSafeArea(.all) - - // MARK: Background for whole app -// .background{ -// Image("peakpx") -// .resizable() -// .scaledToFill() -// .edgesIgnoringSafeArea(.all) -// GlassEffect(material: .sidebar, blendingMode: .withinWindow) -// .edgesIgnoringSafeArea(.all) -// } - // .background(Color("bg").opacity(1)) - // .background(VisualEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all)) } } @@ -188,56 +165,52 @@ struct Header: View { var body: some View { HStack { - Text(title).foregroundStyle(Color("mode")).opacity(0.5) - - HStack { - if hovered { - withAnimation() { - Image(systemName: "arrow.circlepath") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 19, height: 19) - .onTapGesture { - withAnimation { - // Refresh Apps list - appState.reload.toggle() - showPopover = false - let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState) - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - appState.sortedApps = sortedApps - if instantSearch { - loadAllPaths(allApps: sortedApps, appState: appState, locations: locations) { - appState.reload.toggle() - } - } else { - appState.reload.toggle() - } - - } - } - } - .help("Refresh apps") - } - } else { - Text("\(count)") - .font(.system(size: 10)) - .frame(minWidth: count > 99 ? 30 : 20, minHeight: 15) - .padding(2) - .background(Color("mode").opacity(0.1)) - .clipShape(.capsule) - } - } - .onHover { hovering in - withAnimation() { - hovered = hovering - } - } + Text("\(title)").foregroundStyle(Color("mode")).opacity(0.5) + + Text("\(count)") + .font(.system(size: 10)) + .monospacedDigit() + .frame(minWidth: count > 99 ? 30 : 24, minHeight: 17) + .background(Color("mode").opacity(0.1)) + .clipShape(.capsule) + .padding(.leading, 2) Spacer() + if hovered { + Text("REFRESH") + .font(.system(size: 10)) + .monospaced() + .foregroundStyle(Color("mode").opacity(0.8)) + } + + } + .onHover { hovering in + withAnimation() { + hovered = hovering + } + } + .onTapGesture { + withAnimation { + // Refresh Apps list + appState.reload.toggle() + showPopover = false + let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + appState.sortedApps = sortedApps + if instantSearch { + loadAllPaths(allApps: sortedApps, appState: appState, locations: locations) { + appState.reload.toggle() + } + } else { + appState.reload.toggle() + } + + } + } } - .padding(.vertical, 9) - .padding(.leading, 6) -// .background(!glass ? Color("windowBG") : .clear) + .frame(minHeight: 20) + .help("Click header or ⌘+R to refresh apps list") + .padding(5) } } diff --git a/Pearcleaner/Views/TopBar.swift b/Pearcleaner/Views/TopBar.swift index 1da9699..eb19151 100644 --- a/Pearcleaner/Views/TopBar.swift +++ b/Pearcleaner/Views/TopBar.swift @@ -50,13 +50,12 @@ struct TopBar: View { if instantSearch { let reverse = ReversePathsSearcher(appState: appState, locations: locations) reverse.reversePathsSearch() - // reversePathsSearch(appState: appState, locations: locations) } else { loadAllPaths(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true) } } } - .buttonStyle(NavButtonBottomBarStyle(image: "arrow.counterclockwise.circle.fill", help: "Rescan files")) + .buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle.fill", help: "Rescan files")) } if appState.currentView != .zombie { @@ -84,7 +83,7 @@ struct TopBar: View { } } - .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files")) } @@ -95,19 +94,15 @@ struct TopBar: View { appState.appInfo = AppInfo.empty } } - .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", help: "Drop Target", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", help: "Drop Target")) } - - } - } - .padding(.horizontal, 10) - .padding(.top, 15) + .padding(6) } } diff --git a/Pearcleaner/Views/TopBarMini.swift b/Pearcleaner/Views/TopBarMini.swift index 4016148..5fc09a3 100644 --- a/Pearcleaner/Views/TopBarMini.swift +++ b/Pearcleaner/Views/TopBarMini.swift @@ -51,14 +51,14 @@ struct TopBarMini: View { } } - .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files")) Button("") { withAnimation(.easeInOut(duration: 0.5)) { appState.currentView = .apps } } - .buttonStyle(SimpleButtonStyle(icon: "square.grid.3x3.square", help: "Apps List", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "square.grid.3x3.square", help: "Apps List")) } @@ -93,7 +93,7 @@ struct TopBarMini: View { } } - .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "clock.arrow.circlepath", help: "Leftover Files")) Button("") { withAnimation(.easeInOut(duration: 0.5)) { @@ -102,7 +102,7 @@ struct TopBarMini: View { showPopover = false } } - .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", help: "Drop Target", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "plus.square.dashed", help: "Drop Target")) } } diff --git a/Pearcleaner/Views/ZombieView.swift b/Pearcleaner/Views/ZombieView.swift index 509e245..10f3576 100644 --- a/Pearcleaner/Views/ZombieView.swift +++ b/Pearcleaner/Views/ZombieView.swift @@ -21,7 +21,6 @@ struct ZombieView: View { @Binding var showPopover: Bool @Binding var search: String @State private var searchZ: String = "" -// @State private var selectedOption = "Alpha" var regularWin: Bool @State private var elapsedTime = 0 @State private var timer: Timer? = nil @@ -57,21 +56,17 @@ struct ZombieView: View { Spacer() Text("Searching the file system").font(.title3) - .foregroundStyle((.gray.opacity(0.8))) - - HStack { - ProgressView() - .progressViewStyle(.linear) - .frame(width: 400, height: 10) - Text("\(elapsedTime)") - .font(.caption) - .foregroundStyle((.gray.opacity(0.8))) -// Image(systemName: "\(elapsedTime).circle") -// .resizable() -// .aspectRatio(contentMode: .fit) -// .frame(width: 16, height: 16) -// .foregroundStyle((.gray.opacity(0.8))) - } + .foregroundStyle(Color("mode").opacity(0.5)) + + ProgressView() + .progressViewStyle(.linear) + .frame(width: 400, height: 10) + + Text("\(elapsedTime)") + .font(.title).monospacedDigit() + .foregroundStyle(Color("mode").opacity(0.5)) + .opacity(elapsedTime == 0 ? 0 : 1) + .contentTransition(.numericText()) Spacer() @@ -82,7 +77,9 @@ struct ZombieView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .onAppear { self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in - self.elapsedTime += 1 + withAnimation { + self.elapsedTime += 1 + } } } .onDisappear { @@ -109,7 +106,7 @@ struct ZombieView: View { } } } - .buttonStyle(NavButtonBottomBarStyle(image: "arrow.counterclockwise.circle.fill", help: "Rescan files")) + .buttonStyle(SimpleButtonStyle(icon: "arrow.counterclockwise.circle.fill", help: "Rescan files")) Button("Close") { updateOnMain { @@ -119,7 +116,7 @@ struct ZombieView: View { showPopover = false } } - .buttonStyle(NavButtonBottomBarStyle(image: "x.circle.fill", help: "Close")) + .buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close")) } .padding([.horizontal, .top], 5) } @@ -146,7 +143,7 @@ struct ZombieView: View { } Text("Files and folders remaining from previously installed applications") - .font(.callout).foregroundStyle((.gray.opacity(0.8))) + .font(.callout).foregroundStyle(Color("mode").opacity(0.5)) } Spacer() @@ -155,7 +152,7 @@ struct ZombieView: View { Text("\(formatByte(size: filteredAndSortedFiles.1))").font(.title).fontWeight(.bold) Text("\(appState.zombieFile.fileSize.count == 1 ? "\(appState.selectedZombieItems.count) / \(appState.zombieFile.fileSize.count) item" : "\(appState.selectedZombieItems.count) / \(appState.zombieFile.fileSize.count) items")") - .font(.callout).foregroundStyle((.gray.opacity(0.8))) + .font(.callout).foregroundStyle(Color("mode").opacity(0.5)) } } @@ -184,7 +181,7 @@ struct ZombieView: View { Button("") { selectedSortAlpha.toggle() } - .buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: selectedSortAlpha ? "textformat.abc" : "textformat.123", help: selectedSortAlpha ? "Sorted alphabetically" : "Sorted by size")) } @@ -311,7 +308,6 @@ struct ZombieFileDetailsItem: View { .lineLimit(1) .truncationMode(.tail) .opacity(0.5) - // .foregroundStyle(Color("AccentColor")) .help(path.path) } @@ -324,7 +320,7 @@ struct ZombieFileDetailsItem: View { Button("") { NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) } - .buttonStyle(SimpleButtonStyle(icon: "folder.fill", help: "Show in Finder", color: Color("mode"))) + .buttonStyle(SimpleButtonStyle(icon: "folder.fill", help: "Show in Finder")) } .background( diff --git a/Pearcleaner/Windows/PermView.swift b/Pearcleaner/Windows/PermView.swift index 3a2d85d..39ae3c6 100644 --- a/Pearcleaner/Windows/PermView.swift +++ b/Pearcleaner/Windows/PermView.swift @@ -38,10 +38,10 @@ struct PermView: View { .resizable() .scaledToFit() .frame(width: 20, height: 20) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) Text("Full Disk Access permission to find and delete files in system paths") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } HStack(alignment: .top, spacing: 20) { @@ -49,10 +49,10 @@ struct PermView: View { .resizable() .scaledToFit() .frame(width: 20, height: 20) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) Text("Accessibility permission to delete files via Finder") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } HStack(alignment: .top, spacing: 20) { @@ -60,10 +60,10 @@ struct PermView: View { .resizable() .scaledToFit() .frame(width: 20, height: 20) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) Text("Add Pearcleaner in both Privacy panes via the + or by dragging the app over the pane. If the app is already pre-populated in the list, just toggle On if neeeded. Restart app when both permissions are granted") .font(.callout) - .foregroundStyle(.gray) + .foregroundStyle(Color("mode").opacity(0.5)) } } .padding(.horizontal, 20) diff --git a/features.json b/features.json index 11c0e4d..35465d2 100644 --- a/features.json +++ b/features.json @@ -1,4 +1,5 @@ { + "3.5.0": "- Finder Extension: When enabled, you can right click an app in finder and uninstall with Pearcleaner directly|- Theme System: You can now select a base theme color for the application window", "3.3.0": "- Folder Settings: Add more directories where Pearcleaner should search for app files|- Progress: Show progress bar on startup when loading all app files with instant search enabled|- App Icon: Show background color behind app icons in FilesView based on icon's average color mapping|- Progress: Show time counter on regular file search views", "3.2.0": "- Menubar Item: You can choose to access Pearcleaner from the menubar. This will show a slightly modified Mini Mode view.", "3.1.0": "- Homebrew Cleanup: If you install apps using brew, you can now enable homebrew cleanup in the settings to have Pearcleaner alert Homebrew that the app was removed externally from Homebrew and keep the brew app list synced.",