mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 06:09:01 +00:00
v3.7.3
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DD49EF2BABA14400CCBA16 /* Folders.swift */; };
|
||||
C7DE17652BE54F4900197715 /* Updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE17642BE54F4900197715 /* Updater.swift */; };
|
||||
C7DE672B2BA6343D00EB1633 /* MenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */; };
|
||||
C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */; };
|
||||
C7ED86B12BA8A35500F13DE4 /* Interface.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B02BA8A35500F13DE4 /* Interface.swift */; };
|
||||
C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7ED86B22BA8ED8800F13DE4 /* Trash.swift */; };
|
||||
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F539372AF60865007DF1B2 /* Utilities.swift */; };
|
||||
@@ -145,6 +146,7 @@
|
||||
C7DD49EF2BABA14400CCBA16 /* Folders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Folders.swift; sourceTree = "<group>"; };
|
||||
C7DE17642BE54F4900197715 /* Updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updater.swift; sourceTree = "<group>"; };
|
||||
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarItem.swift; sourceTree = "<group>"; };
|
||||
C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionBuilderView.swift; sourceTree = "<group>"; };
|
||||
C7ED86B02BA8A35500F13DE4 /* Interface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interface.swift; sourceTree = "<group>"; };
|
||||
C7ED86B22BA8ED8800F13DE4 /* Trash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Trash.swift; sourceTree = "<group>"; };
|
||||
C7F539372AF60865007DF1B2 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
|
||||
@@ -309,6 +311,7 @@
|
||||
C7FB173A2B96321300B96F9A /* AppsListView.swift */,
|
||||
C7FEBA102BDC422200AE195F /* AppSearchView.swift */,
|
||||
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
|
||||
C7EA06322C1CBE2000F872FC /* ConditionBuilderView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -471,6 +474,7 @@
|
||||
C7DE17652BE54F4900197715 /* Updater.swift in Sources */,
|
||||
C7DD49EE2BAB7F6000CCBA16 /* ReversePathsFetch.swift in Sources */,
|
||||
C7045A322B068AD700376976 /* NewWin.swift in Sources */,
|
||||
C7EA06332C1CBE2000F872FC /* ConditionBuilderView.swift in Sources */,
|
||||
C7DD49F02BABA14400CCBA16 /* Folders.swift in Sources */,
|
||||
C7ED86B32BA8ED8800F13DE4 /* Trash.swift in Sources */,
|
||||
C7D31D512AFF00F300C7ED9E /* Locations.swift in Sources */,
|
||||
@@ -568,8 +572,8 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APP_BUILD = 47;
|
||||
APP_VERSION = 3.7.2;
|
||||
APP_BUILD = 48;
|
||||
APP_VERSION = 3.7.3;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@@ -638,8 +642,8 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APP_BUILD = 47;
|
||||
APP_VERSION = 3.7.2;
|
||||
APP_BUILD = 48;
|
||||
APP_VERSION = 3.7.3;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
|
||||
@@ -153,20 +153,18 @@ class AppPathFinder {
|
||||
|
||||
for condition in conditions {
|
||||
if bundleIdentifierL.contains(condition.bundle_id) {
|
||||
// Exclude and include keywords
|
||||
let hasIncludeKeyword = condition.include.contains(where: itemL.contains)
|
||||
// Exclude keywords
|
||||
let hasExcludeKeyword = condition.exclude.contains(where: itemL.contains)
|
||||
|
||||
if hasExcludeKeyword {
|
||||
return false
|
||||
}
|
||||
// Include keywords
|
||||
let hasIncludeKeyword = condition.include.contains(where: itemL.contains)
|
||||
if hasIncludeKeyword {
|
||||
if !condition.exclude.contains(where: itemL.contains) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if self.appInfo.webApp {
|
||||
@@ -230,7 +228,7 @@ class AppPathFinder {
|
||||
}
|
||||
|
||||
|
||||
private func handleOutliers() -> [URL] {
|
||||
private func handleOutliers(include: Bool = true) -> [URL] {
|
||||
var outliers: [URL] = []
|
||||
let bundleIdentifier = self.appInfo.bundleIdentifier.pearFormat()
|
||||
|
||||
@@ -239,14 +237,24 @@ class AppPathFinder {
|
||||
bundleIdentifier.contains(condition.bundle_id)
|
||||
}
|
||||
|
||||
|
||||
for condition in matchingConditions {
|
||||
if include {
|
||||
// Handle includeForce
|
||||
if let forceIncludes = condition.includeForce {
|
||||
for path in forceIncludes {
|
||||
if let url = URL(string: path), FileManager.default.fileExists(atPath: url.path) {
|
||||
outliers.append(url)
|
||||
outliers.append(path)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle excludeForce
|
||||
if let excludeForce = condition.excludeForce {
|
||||
for path in excludeForce {
|
||||
outliers.append(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return outliers
|
||||
@@ -256,6 +264,7 @@ class AppPathFinder {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let allContainers = self.getAllContainers(bundleURL: self.appInfo.path)
|
||||
let outliers = self.handleOutliers()
|
||||
let outliersEx = self.handleOutliers(include: false)
|
||||
var tempCollection: [URL] = []
|
||||
self.collectionAccessQueue.sync {
|
||||
tempCollection = self.collection
|
||||
@@ -263,6 +272,12 @@ class AppPathFinder {
|
||||
tempCollection.append(contentsOf: allContainers)
|
||||
tempCollection.append(contentsOf: outliers)
|
||||
|
||||
// Remove URLs based on outliersExcludes
|
||||
let excludePaths = outliersEx.map { $0.path }
|
||||
tempCollection.removeAll { url in
|
||||
excludePaths.contains(url.path)
|
||||
}
|
||||
|
||||
// Sort and standardize URLs to ensure consistent comparisons
|
||||
let sortedCollection = tempCollection.map { $0.standardizedFileURL }.sorted(by: { $0.path < $1.path })
|
||||
var filteredCollection: [URL] = []
|
||||
|
||||
@@ -6,13 +6,32 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
|
||||
struct Condition: Decodable {
|
||||
struct Condition: Codable {
|
||||
var bundle_id: String
|
||||
var include: [String]
|
||||
var exclude: [String]
|
||||
var includeForce: [String]?
|
||||
var includeForce: [URL]?
|
||||
var excludeForce: [URL]?
|
||||
|
||||
init(bundle_id: String, include: [String], exclude: [String], includeForce: [String]? = nil, excludeForce: [String]? = nil) {
|
||||
self.bundle_id = bundle_id.pearFormat()
|
||||
self.include = include.map { $0.pearFormat() }
|
||||
self.exclude = exclude.map { $0.pearFormat() }
|
||||
self.includeForce = includeForce?.compactMap { path in
|
||||
if let url = URL(string: path), FileManager.default.fileExists(atPath: url.path) {
|
||||
return url
|
||||
}
|
||||
return nil
|
||||
}
|
||||
self.excludeForce = excludeForce?.compactMap { path in
|
||||
if let url = URL(string: path), FileManager.default.fileExists(atPath: url.path) {
|
||||
return url
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SkipCondition {
|
||||
@@ -25,70 +44,75 @@ struct SkipCondition {
|
||||
// Conditions for some apps that need to include/exclude certain files/folders when names are more complicated
|
||||
var conditions: [Condition] = [
|
||||
Condition(
|
||||
bundle_id: "comappledtxcode",
|
||||
include: ["comappledt", "xcode", "simulator"],
|
||||
exclude: ["comrobotsandpencilsxcodesapp", "comoneminutegamesxcodecleaner", "iohyperappxcodecleaner", "xcodesjson"],
|
||||
bundle_id: "com.apple.dt.xcode",
|
||||
include: ["com.apple.dt", "xcode", "simulator"],
|
||||
exclude: ["com.robotsandpencils.xcodesapp", "com.oneminutegames.xcodecleaner", "io.hyperapp.xcodecleaner", "xcodes.json"],
|
||||
includeForce: ["\(home)/Library/Containers/com.apple.iphonesimulator.ShareExtension"]
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "comrobotsandpencilsxcodesapp",
|
||||
bundle_id: "com.robotsandpencils.xcodesapp",
|
||||
include: [],
|
||||
exclude: ["comappledtxcode", "comoneminutegamesxcodecleaner", "iohyperappxcodecleaner"]
|
||||
exclude: ["com.apple.dt.xcode", "com.oneminutegames.xcodecleaner", "io.hyperapp.xcodecleaner"]
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "iohyperappxcodecleaner",
|
||||
bundle_id: "io.hyperapp.xcodecleaner",
|
||||
include: [],
|
||||
exclude: ["comrobotsandpencilsxcodesapp", "comoneminutegamesxcodecleaner", "comappledtxcode", "xcodesjson"]
|
||||
exclude: ["com.robotsandpencils.xcodesapp", "com.oneminutegames.xcodecleaner", "com.apple.dt.xcode", "xcodes.json"]
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "uszoomxos",
|
||||
bundle_id: "us.zoom.xos",
|
||||
include: ["zoom"],
|
||||
exclude: []
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "combravebrowser",
|
||||
bundle_id: "com.brave.browser",
|
||||
include: ["brave"],
|
||||
exclude: []
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "comoktamobile",
|
||||
bundle_id: "com.okta.mobile",
|
||||
include: ["okta"],
|
||||
exclude: []
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "comgooglechrome",
|
||||
bundle_id: "com.google.chrome",
|
||||
include: ["google", "chrome"],
|
||||
exclude: ["iterm", "chromefeaturestate"]
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "commicrosoftedgemac",
|
||||
bundle_id: "com.microsoft.edgemac",
|
||||
include: ["microsoft"],
|
||||
exclude: ["vscode", "rdc", "appcenter", "office", "oneauth"]
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "orgmozillafirefox",
|
||||
bundle_id: "org.mozilla.firefox",
|
||||
include: ["mozilla", "firefox"],
|
||||
exclude: []
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "comlogioptionsplus",
|
||||
bundle_id: "org.mozilla.firefox.nightly",
|
||||
include: ["mozilla", "firefox"],
|
||||
exclude: []
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "com.logi.optionsplus",
|
||||
include: ["logi"],
|
||||
exclude: ["login", "logic"],
|
||||
includeForce: []
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "commicrosoftvscode",
|
||||
bundle_id: "com.microsoft.vscode",
|
||||
include: ["vscode"],
|
||||
exclude: [],
|
||||
includeForce: ["\(home)/Library/Application Support/Code"]
|
||||
includeForce: ["\(home)/Library/Application Support/Code/"]
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "comfacebookarchondeveloperid",
|
||||
include: ["archonloginhelper"],
|
||||
bundle_id: "com.facebook.archon.developerid",
|
||||
include: ["archon.loginhelper"],
|
||||
exclude: []
|
||||
),
|
||||
Condition(
|
||||
bundle_id: "euexelbanstats",
|
||||
bundle_id: "eu.exelban.stats",
|
||||
include: [],
|
||||
exclude: ["video"]
|
||||
),
|
||||
@@ -114,36 +138,59 @@ let skipConditions: [SkipCondition] = [
|
||||
let skipReverse = ["apple", "temporary", "btserver", "proapps", "scripteditor", "ilife", "livefsd", "siritoday", "addressbook", "animoji", "appstore", "askpermission", "callhistory", "clouddocs", "diskimages", "dock", "facetime", "fileprovider", "instruments", "knowledge", "mobilesync", "syncservices", "homeenergyd", "icloud", "icdd", "networkserviceproxy", "familycircle", "geoservices", "installation", "passkit", "sharedimagecache", "desktop", "mbuseragent", "swiftpm", "baseband", "coresimulator", "photoslegacyupgrade", "photosupgrade", "siritts", "ipod", "globalpreferences", "apmanalytics", "apmexperiment", "avatarcache", "byhost", "contextstoreagent", "mobilemeaccounts", "intentbuilderc", "loginwindow", "momc", "replayd", "sharedfilelistd", "clang", "audiocomponent", "csexattrcryptoservice", "livetranscriptionagent", "sandboxhelper", "statuskitagent", "betaenrollmentd", "contentlinkingd", "diagnosticextensionsd", "gamed", "heard", "homed", "itunescloudd", "lldb", "mds", "mediaanalysisd", "metrickitd", "mobiletimerd", "proactived", "ptpcamerad", "studentd", "talagent", "watchlistd", "apptranslocation", "xcrun", "ds_store", "caches", "crashreporter", "trash", "pearcleaner", "amsdatamigratortool", "arfilecache", "assistant", "chromium", "cloudkit", "webkit", "databases", "diagnostic", "cache", "gamekit", "homebrew", "logi", "microsoft", "mozilla", "sync", "google", "sentinel", "hexnode", "sentry", "tvappservices", "reminders"]
|
||||
|
||||
|
||||
// Function to load additional conditions from a GitHub JSON file
|
||||
func loadConditionsFromGitHub() {
|
||||
let url = URL(string: "https://api.github.com/repos/alienator88/Pearcleaner/contents/conditions.json")!
|
||||
var request = URLRequest(url: url)
|
||||
request.setValue("application/vnd.github.VERSION.raw", forHTTPHeaderField: "Accept")
|
||||
request.setValue("2022-11-28", forHTTPHeaderField: "X-GitHub-Api-Version")
|
||||
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
if let data = data {
|
||||
do {
|
||||
// Assuming the JSON structure directly maps to an array of Condition
|
||||
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
if let conditionArray = jsonObject as? [[String: Any]] {
|
||||
let jsonData = try JSONSerialization.data(withJSONObject: conditionArray, options: [])
|
||||
let additionalConditions = try JSONDecoder().decode([Condition].self, from: jsonData)
|
||||
|
||||
if !additionalConditions.isEmpty {
|
||||
DispatchQueue.main.async {
|
||||
conditions += additionalConditions
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printOS("The data format is incorrect or empty for GitHub conditions processing.")
|
||||
}
|
||||
} catch {
|
||||
printOS("Failed to decode conditions JSON: \(error.localizedDescription)")
|
||||
}
|
||||
} else {
|
||||
printOS("Failed to fetch conditions data: \(error?.localizedDescription ?? "Unknown error")")
|
||||
}
|
||||
}.resume()
|
||||
|
||||
|
||||
// Store and load conditions locally via SwiftData
|
||||
class ConditionManager {
|
||||
static let shared = ConditionManager()
|
||||
|
||||
private init() {
|
||||
loadConditions()
|
||||
}
|
||||
|
||||
// Function to save a condition
|
||||
func saveCondition(_ condition: Condition) {
|
||||
if condition.include.isEmpty && condition.exclude.isEmpty && (condition.includeForce?.isEmpty ?? true) {
|
||||
deleteCondition(bundle_id: condition.bundle_id)
|
||||
return
|
||||
}
|
||||
let defaults = UserDefaults.standard
|
||||
let encoder = JSONEncoder()
|
||||
let key = "Condition-\(condition.bundle_id)"
|
||||
|
||||
if let encoded = try? encoder.encode(condition) {
|
||||
defaults.set(encoded, forKey: key)
|
||||
conditions.append(condition)
|
||||
}
|
||||
}
|
||||
|
||||
// Function to delete a condition from defaults and conditions variable
|
||||
func deleteCondition(bundle_id: String) {
|
||||
let defaults = UserDefaults.standard
|
||||
let key = "Condition-\(bundle_id.pearFormat())"
|
||||
|
||||
// Remove from UserDefaults
|
||||
defaults.removeObject(forKey: key)
|
||||
|
||||
// Remove from conditions variable
|
||||
conditions.removeAll { $0.bundle_id == bundle_id.pearFormat() }
|
||||
}
|
||||
|
||||
// Function to load a condition and append to the global variable
|
||||
func loadConditions() {
|
||||
let defaults = UserDefaults.standard
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
for (key, value) in defaults.dictionaryRepresentation() {
|
||||
if key.starts(with: "Condition-"), let savedCondition = value as? Data {
|
||||
if let loadedCondition = try? decoder.decode(Condition.self, from: savedCondition) {
|
||||
conditions.append(loadedCondition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ class Locations: ObservableObject {
|
||||
self.tempDir = tempDir
|
||||
|
||||
self.apps = Category(name: "Apps", paths: [
|
||||
"\(home)",
|
||||
"\(home)/Library",
|
||||
"\(home)/Library/Application Scripts",
|
||||
"\(home)/Library/Application Support",
|
||||
|
||||
@@ -630,6 +630,20 @@ struct AnimatedSearchStyle: TextFieldStyle {
|
||||
}
|
||||
|
||||
|
||||
struct RoundedTextFieldStyle: TextFieldStyle {
|
||||
func _body(configuration: TextField<Self._Label>) -> some View {
|
||||
configuration
|
||||
.padding(8)
|
||||
.cornerRadius(6)
|
||||
.textFieldStyle(.plain)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.strokeBorder(Color("mode").opacity(0.4), lineWidth: 0.8)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct GlassEffect: NSViewRepresentable {
|
||||
var material: NSVisualEffectView.Material // Choose the material you want
|
||||
|
||||
@@ -52,10 +52,10 @@ func loadGithubReleases(appState: AppState, manual: Bool = false, releaseOnly: B
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func checkForUpdate(appState: AppState, manual: Bool = false) {
|
||||
func checkForUpdate(appState: AppState, manual: Bool) {
|
||||
guard let latestRelease = appState.releases.first else { return }
|
||||
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
if latestRelease.tag_name > currentVersion ?? "" {
|
||||
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
||||
if latestRelease.tag_name > currentVersion {
|
||||
updateOnMain {
|
||||
appState.updateAvailable = true
|
||||
}
|
||||
@@ -198,14 +198,6 @@ enum UpdateFrequency: String, CaseIterable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
//func updateNextUpdateDate() {
|
||||
// @AppStorage("settings.updater.updateFrequency") var updateFrequency: UpdateFrequency = .daily
|
||||
// @AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
||||
//
|
||||
// guard let updateInterval = updateFrequency.interval else { return }
|
||||
// let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(updateInterval))
|
||||
// nextUpdateDate = newUpdateDate.timeIntervalSinceReferenceDate
|
||||
//}
|
||||
|
||||
func checkAndUpdateIfNeeded(appState: AppState) {
|
||||
@AppStorage("settings.updater.updateFrequency") var updateFrequency: UpdateFrequency = .daily
|
||||
@@ -230,7 +222,7 @@ func checkAndUpdateIfNeeded(appState: AppState) {
|
||||
|
||||
func updateApp(appState: AppState) {
|
||||
// Perform your update logic here
|
||||
printOS("Updater: performing update")
|
||||
printOS("Updater: checking for update")
|
||||
loadGithubReleases(appState: appState)
|
||||
}
|
||||
|
||||
@@ -243,54 +235,6 @@ func isSameDay(date1: Date, date2: Date) -> Bool {
|
||||
return Calendar.current.isDate(date1, inSameDayAs: date2)
|
||||
}
|
||||
|
||||
//func updateNextUpdateDate() {
|
||||
// @AppStorage("settings.updater.updateTimeframe") var updateTimeframe: Int = 1
|
||||
// @AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
||||
// let updateSeconds = updateTimeframe.daysToSeconds
|
||||
// let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(updateSeconds))
|
||||
// nextUpdateDate = newUpdateDate.timeIntervalSinceReferenceDate
|
||||
//}
|
||||
//
|
||||
//func checkAndUpdateIfNeeded(appState: AppState) {
|
||||
// @AppStorage("settings.updater.updateTimeframe") var updateTimeframe: Int = 1
|
||||
// @AppStorage("settings.updater.nextUpdateDate") var nextUpdateDate = Date.now.timeIntervalSinceReferenceDate
|
||||
//
|
||||
// let updateSeconds = updateTimeframe.daysToSeconds
|
||||
// let now = Date()
|
||||
//
|
||||
// // Retrieve the next update date from UserDefaults
|
||||
// let nextUpdateDateLocal = Date(timeIntervalSinceReferenceDate: nextUpdateDate)
|
||||
//// let nextUpdateDate = UserDefaults.standard.object(forKey: "settings.updater.nextUpdateDate") as? Date
|
||||
//
|
||||
// // If there's no stored next update date or it's in the past, update immediately
|
||||
// if !isSameDay(date1: nextUpdateDateLocal, date2: now) {
|
||||
// // Next update date is in the future, no need to update
|
||||
// printOS("Updater: next update date is in the future, skipping")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // Update immediately and set next update date
|
||||
// updateApp(appState: appState)
|
||||
// setNextUpdateDate(interval: updateSeconds)
|
||||
//}
|
||||
//
|
||||
//func updateApp(appState: AppState) {
|
||||
// // Perform your update logic here
|
||||
// printOS("Updater: performing update")
|
||||
// loadGithubReleases(appState: appState)
|
||||
//}
|
||||
//
|
||||
//func setNextUpdateDate(interval: TimeInterval) {
|
||||
// let newUpdateDate = Calendar.current.startOfDay(for: Date().addingTimeInterval(interval))
|
||||
// UserDefaults.standard.set(newUpdateDate.timeIntervalSinceReferenceDate, forKey: "settings.updater.nextUpdateDate")
|
||||
//// UserDefaults.standard.set(newUpdateDate, forKey: "settings.updater.nextUpdateDate")
|
||||
//}
|
||||
//
|
||||
//func isSameDay(date1: Date, date2: Date) -> Bool {
|
||||
// return Calendar.current.isDate(date1, inSameDayAs: date2)
|
||||
//}
|
||||
|
||||
|
||||
|
||||
// --- Updater Badge View
|
||||
|
||||
|
||||
@@ -427,6 +427,31 @@ extension String {
|
||||
}
|
||||
}
|
||||
|
||||
// --- Returns comma separated string as array of strings
|
||||
extension String {
|
||||
func toConditionFormat() -> [String] {
|
||||
if self.isEmpty {
|
||||
return []
|
||||
}
|
||||
return self.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Overload the greater than operator ">" to do a semantic check on the string versions
|
||||
extension String {
|
||||
func versionStringToTuple() -> (Int, Int, Int) {
|
||||
let components = self.split(separator: ".").compactMap { Int($0) }
|
||||
return (components[0], components[1], components[2])
|
||||
}
|
||||
|
||||
static func > (lhs: String, rhs: String) -> Bool {
|
||||
let lhsVersion = lhs.versionStringToTuple()
|
||||
let rhsVersion = rhs.versionStringToTuple()
|
||||
return lhsVersion > rhsVersion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Trash Relationship ---
|
||||
//extension FileManager {
|
||||
|
||||
@@ -27,7 +27,7 @@ struct PearcleanerApp: App {
|
||||
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "trash"
|
||||
@State private var search = ""
|
||||
@State private var showPopover: Bool = false
|
||||
|
||||
let conditionManager = ConditionManager.shared
|
||||
|
||||
|
||||
var body: some Scene {
|
||||
@@ -127,7 +127,7 @@ struct PearcleanerApp: App {
|
||||
// Get new features
|
||||
getFeatures(appState: appState, features: $features)
|
||||
// Load extra conditions from GitHub
|
||||
loadConditionsFromGitHub()
|
||||
// loadConditionsFromGitHub() // REPLACE THIS WITH NEW CONDITION MANAGER
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
||||
extension NSTextView {
|
||||
open override var frame: CGRect {
|
||||
didSet {
|
||||
insertionPointColor = .clear
|
||||
insertionPointColor = NSColor(Color("mode").opacity(0.2))//.clear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// ConditionBuilderView.swift
|
||||
// Pearcleaner
|
||||
//
|
||||
// Created by Alin Lupascu on 6/14/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct ConditionBuilderView: View {
|
||||
@Binding var showAlert:Bool
|
||||
@State private var include = ""
|
||||
@State private var exclude = ""
|
||||
@State private var paths = ""
|
||||
@State private var pathsEx = ""
|
||||
@State var bundle: String
|
||||
@State private var conditionExists = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 10) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("Condition Builder")
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
Button("Close") {
|
||||
showAlert = false
|
||||
}
|
||||
.buttonStyle(SimpleButtonStyle(icon: "x.circle", iconFlip: "x.circle.fill", help: "Close"))
|
||||
}
|
||||
|
||||
Divider()
|
||||
Spacer()
|
||||
InfoButton(text: "Some files/folders are not similar to the app name or bundle id, causing Pearcleaner to either not find them or find unrelated files. \nTo combat this, you may create a custom condition for each application bundle using keywords or direct paths. \n\n- Can add file/folder keywords that you want to either include or exclude in fuzzy searches. \n\n- Can explicitly add or remove a full Finder path to search results if you want a direct search.", label: "Instructions", edge: .bottom)
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
Text("Include Keywords:").font(.callout)
|
||||
Spacer()
|
||||
}
|
||||
HStack {
|
||||
TextField("keyword-1, keyword-2", text: $include)
|
||||
.textFieldStyle(RoundedTextFieldStyle())
|
||||
Button("Clear") {
|
||||
include = ""
|
||||
}
|
||||
.buttonStyle(SimpleButtonStyle(icon: "xmark.circle.fill", help: "Clear", size: 15))
|
||||
}
|
||||
|
||||
HStack {
|
||||
Text("Exclude Keywords:").font(.callout)
|
||||
Spacer()
|
||||
}
|
||||
HStack {
|
||||
TextField("keyword-1, keyword-2", text: $exclude)
|
||||
.textFieldStyle(RoundedTextFieldStyle())
|
||||
Button("Clear") {
|
||||
exclude = ""
|
||||
}
|
||||
.buttonStyle(SimpleButtonStyle(icon: "xmark.circle.fill", help: "Clear", size: 15))
|
||||
}
|
||||
|
||||
HStack {
|
||||
Text("Add Direct Paths:").font(.callout)
|
||||
Spacer()
|
||||
}
|
||||
HStack {
|
||||
TextField("/Full/Path/example-1.txt, /Full/Path/example-2.txt", text: $paths)
|
||||
.textFieldStyle(RoundedTextFieldStyle())
|
||||
Button("Clear") {
|
||||
paths = ""
|
||||
}
|
||||
.buttonStyle(SimpleButtonStyle(icon: "xmark.circle.fill", help: "Clear", size: 15))
|
||||
}
|
||||
HStack {
|
||||
Text("Remove Direct Paths:").font(.callout)
|
||||
Spacer()
|
||||
}
|
||||
HStack {
|
||||
TextField("/Full/Path/example-1.txt, /Full/Path/example-2.txt", text: $pathsEx)
|
||||
.textFieldStyle(RoundedTextFieldStyle())
|
||||
Button("Clear") {
|
||||
pathsEx = ""
|
||||
}
|
||||
.buttonStyle(SimpleButtonStyle(icon: "xmark.circle.fill", help: "Clear", size: 15))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Add/Save") {
|
||||
showAlert = false
|
||||
let newCondition = Condition(bundle_id: bundle, include: include.toConditionFormat(), exclude: exclude.toConditionFormat(), includeForce: paths.toConditionFormat(), excludeForce: pathsEx.toConditionFormat())
|
||||
ConditionManager.shared.saveCondition(newCondition)
|
||||
}
|
||||
.buttonStyle(SimpleButtonStyle(icon: "plus.square.fill", label: conditionExists ? "Save" : "Add", help: "Save the condition for this application"))
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Remove") {
|
||||
showAlert = false
|
||||
include = ""
|
||||
exclude = ""
|
||||
paths = ""
|
||||
pathsEx = ""
|
||||
ConditionManager.shared.deleteCondition(bundle_id: bundle)
|
||||
}
|
||||
.buttonStyle(SimpleButtonStyle(icon: "minus.square.fill", label: "Remove", help: "Remove the condition from this application"))
|
||||
.disabled(!conditionExists)
|
||||
|
||||
Spacer()
|
||||
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(15)
|
||||
.frame(width: 500, height: 500)
|
||||
.background(GlassEffect(material: .hudWindow, blendingMode: .behindWindow))
|
||||
.onAppear {
|
||||
loadCondition()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadCondition() {
|
||||
let defaults = UserDefaults.standard
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
let key = "Condition-\(bundle.pearFormat())"
|
||||
if let savedCondition = defaults.object(forKey: key) as? Data {
|
||||
if let loadedCondition = try? decoder.decode(Condition.self, from: savedCondition) {
|
||||
include = loadedCondition.include.joined(separator: ", ")
|
||||
exclude = loadedCondition.exclude.joined(separator: ", ")
|
||||
if let includeForce = loadedCondition.includeForce {
|
||||
paths = includeForce.map { $0.absoluteString }.joined(separator: ", ")
|
||||
}
|
||||
if let excludeForce = loadedCondition.excludeForce {
|
||||
pathsEx = excludeForce.map { $0.absoluteString }.joined(separator: ", ")
|
||||
}
|
||||
conditionExists = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ struct FilesView: View {
|
||||
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
|
||||
@AppStorage("settings.general.filesWarning") private var warning: Bool = false
|
||||
@State private var showAlert = false
|
||||
@State private var showConditionBuilder = false
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Binding var showPopover: Bool
|
||||
@Binding var search: String
|
||||
@@ -277,6 +278,14 @@ struct FilesView: View {
|
||||
.padding(.top)
|
||||
|
||||
|
||||
Button("Builder") {
|
||||
showConditionBuilder = true
|
||||
}
|
||||
.buttonStyle(SimpleButtonStyle(icon: "hammer.fill", help: "Condition Builder", size: 14))
|
||||
.padding(.top)
|
||||
.padding(.trailing, 5)
|
||||
|
||||
|
||||
Button("\(sizeType == "Logical" ? totalSelectedSize.logical : sizeType == "Finder" ? totalSelectedSize.finder : totalSelectedSize.real)") {
|
||||
Task {
|
||||
|
||||
@@ -389,6 +398,9 @@ struct FilesView: View {
|
||||
.frame(width: 400, height: 200)
|
||||
.background(GlassEffect(material: .hudWindow, blendingMode: .behindWindow))
|
||||
})
|
||||
.sheet(isPresented: $showConditionBuilder, content: {
|
||||
ConditionBuilderView(showAlert: $showConditionBuilder, bundle: appState.appInfo.bundleIdentifier)
|
||||
})
|
||||
.onAppear {
|
||||
if !warning {
|
||||
showAlert = true
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[
|
||||
{
|
||||
"bundle_id": "orgmozillanightly",
|
||||
"include": ["mozilla", "firefox"],
|
||||
"exclude": [],
|
||||
"includeForce": []
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user