mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 06:09:01 +00:00
Updates
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; };
|
||||
C76D084A2AF83C6E00D07867 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08492AF83C6E00D07867 /* General.swift */; };
|
||||
C76D08512AF850F300D07867 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08502AF850F300D07867 /* About.swift */; };
|
||||
C76D08552AF89CDE00D07867 /* AppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08542AF89CDE00D07867 /* AppListView.swift */; };
|
||||
C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08542AF89CDE00D07867 /* RegularMode.swift */; };
|
||||
C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */; };
|
||||
C77B90082AF18E2F009CC655 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C77B90072AF18E2F009CC655 /* Assets.xcassets */; };
|
||||
C77B90162AF19377009CC655 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90152AF19377009CC655 /* AppState.swift */; };
|
||||
@@ -75,7 +75,7 @@
|
||||
C76D08472AF83C3F00D07867 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = "<group>"; };
|
||||
C76D08492AF83C6E00D07867 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = "<group>"; };
|
||||
C76D08502AF850F300D07867 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
|
||||
C76D08542AF89CDE00D07867 /* AppListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppListView.swift; sourceTree = "<group>"; };
|
||||
C76D08542AF89CDE00D07867 /* RegularMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegularMode.swift; sourceTree = "<group>"; };
|
||||
C77B90002AF18E2E009CC655 /* Pearcleaner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pearcleaner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PearcleanerApp.swift; sourceTree = "<group>"; };
|
||||
C77B90072AF18E2F009CC655 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
@@ -219,7 +219,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C77B90222AF2D616009CC655 /* FilesView.swift */,
|
||||
C76D08542AF89CDE00D07867 /* AppListView.swift */,
|
||||
C76D08542AF89CDE00D07867 /* RegularMode.swift */,
|
||||
C7D31D472AFEB23700C7ED9E /* TopBar.swift */,
|
||||
C7045A292B03FAF000376976 /* TopBarMini.swift */,
|
||||
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
|
||||
@@ -360,7 +360,7 @@
|
||||
C7D31D482AFEB23700C7ED9E /* TopBar.swift in Sources */,
|
||||
C7ACADE62B92A737000B5845 /* Features.swift in Sources */,
|
||||
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */,
|
||||
C76D08552AF89CDE00D07867 /* AppListView.swift in Sources */,
|
||||
C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */,
|
||||
C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */,
|
||||
C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */,
|
||||
C77B90232AF2D616009CC655 /* FilesView.swift in Sources */,
|
||||
|
||||
@@ -38,13 +38,13 @@ struct AppCommands: Commands {
|
||||
}
|
||||
let sortedApps = getSortedApps()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
appState.sortedApps.userApps = []
|
||||
appState.sortedApps.systemApps = []
|
||||
appState.sortedApps.userApps = sortedApps.userApps
|
||||
appState.sortedApps.systemApps = sortedApps.systemApps
|
||||
appState.sortedApps = []
|
||||
// appState.sortedApps.systemApps = []
|
||||
appState.sortedApps = sortedApps
|
||||
// appState.sortedApps.systemApps = sortedApps.systemApps
|
||||
if instantSearch {
|
||||
Task(priority: .high){
|
||||
loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations)
|
||||
loadAllPaths(allApps: sortedApps, appState: appState, locations: locations)
|
||||
}
|
||||
}
|
||||
appState.reload.toggle()
|
||||
@@ -73,8 +73,7 @@ struct AppCommands: Commands {
|
||||
undoTrash(appState: appState) {
|
||||
let sortedApps = getSortedApps()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
appState.sortedApps.userApps = sortedApps.userApps
|
||||
appState.sortedApps.systemApps = sortedApps.systemApps
|
||||
appState.sortedApps = sortedApps
|
||||
// if instantSearch {
|
||||
// loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations)
|
||||
// }
|
||||
|
||||
@@ -15,7 +15,7 @@ class AppState: ObservableObject
|
||||
@Published var appInfo: AppInfo
|
||||
@Published var appInfoStore: [AppInfo] = []
|
||||
@Published var zombieFile: ZombieFile
|
||||
@Published var sortedApps: (userApps: [AppInfo], systemApps: [AppInfo]) = ([], [])
|
||||
@Published var sortedApps: [AppInfo] = []
|
||||
@Published var selectedItems = Set<URL>()
|
||||
@Published var selectedZombieItems = Set<URL>()
|
||||
@Published var alertType = AlertType.off
|
||||
@@ -41,6 +41,7 @@ class AppState: ObservableObject
|
||||
appIcon: nil,
|
||||
webApp: false,
|
||||
wrapped: false,
|
||||
system: false,
|
||||
files: [],
|
||||
fileSize: [:],
|
||||
fileIcon: [:]
|
||||
@@ -68,6 +69,7 @@ struct AppInfo: Identifiable, Equatable, Hashable {
|
||||
let appIcon: NSImage?
|
||||
let webApp: Bool
|
||||
let wrapped: Bool
|
||||
let system: Bool
|
||||
var files: [URL]
|
||||
var fileSize: [URL:Int64]
|
||||
var fileIcon: [URL:NSImage?]
|
||||
@@ -77,7 +79,7 @@ struct AppInfo: Identifiable, Equatable, Hashable {
|
||||
}
|
||||
|
||||
|
||||
static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil, webApp: false, wrapped: false, files: [], fileSize: [:], fileIcon: [:])
|
||||
static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil, webApp: false, wrapped: false, system: false, files: [], fileSize: [:], fileIcon: [:])
|
||||
|
||||
}
|
||||
|
||||
|
||||
+87
-134
@@ -8,46 +8,27 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// Get list of apps and sort it
|
||||
func getSortedApps() -> (systemApps: [AppInfo], userApps: [AppInfo]) {
|
||||
let apps = getApplications()
|
||||
let sortedSystemApps = apps.systemApps
|
||||
.compactMap { getAppInfo(atPath: $0) }
|
||||
.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() }
|
||||
let sortedUserApps = apps.userApps
|
||||
.compactMap { getAppInfo(atPath: $0) }
|
||||
.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() }
|
||||
|
||||
return (systemApps: sortedSystemApps, userApps: sortedUserApps)
|
||||
}
|
||||
|
||||
|
||||
// Get all applications from /Applications and ~/Applications
|
||||
func getApplications() -> (systemApps: [URL], userApps: [URL]) {
|
||||
// Get all apps from /Applications and ~/Applications
|
||||
func getSortedApps() -> [AppInfo] {
|
||||
let fileManager = FileManager.default
|
||||
|
||||
// Define the paths for system and user applications
|
||||
let systemAppsPath = "/Applications"
|
||||
let userAppsPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Applications").path
|
||||
|
||||
var systemApps: [URL] = []
|
||||
var userApps: [URL] = []
|
||||
var apps: [URL] = []
|
||||
|
||||
func collectAppPaths(at directoryPath: String, isSystem: Bool) {
|
||||
func collectAppPaths(at directoryPath: String) {
|
||||
do {
|
||||
let appURLs = try fileManager.contentsOfDirectory(at: URL(fileURLWithPath: directoryPath), includingPropertiesForKeys: nil, options: [])
|
||||
|
||||
for appURL in appURLs {
|
||||
if appURL.pathExtension == "app" {
|
||||
// Add the path to the appropriate list
|
||||
if isSystem && !isRestricted(atPath: appURL) {
|
||||
systemApps.append(appURL)
|
||||
} else if !isSystem && !isRestricted(atPath: appURL) {
|
||||
userApps.append(appURL)
|
||||
}
|
||||
if appURL.pathExtension == "app" && !isRestricted(atPath: appURL) {
|
||||
// Add the path to the array
|
||||
apps.append(appURL)
|
||||
} else if appURL.hasDirectoryPath {
|
||||
// If it's a directory, recursively explore it
|
||||
collectAppPaths(at: appURL.path, isSystem: isSystem)
|
||||
collectAppPaths(at: appURL.path)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -56,33 +37,57 @@ func getApplications() -> (systemApps: [URL], userApps: [URL]) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Collect system applications
|
||||
collectAppPaths(at: systemAppsPath, isSystem: true)
|
||||
collectAppPaths(at: systemAppsPath)
|
||||
|
||||
// Collect user applications
|
||||
collectAppPaths(at: userAppsPath, isSystem: false)
|
||||
collectAppPaths(at: userAppsPath)
|
||||
|
||||
// Filter out unwanted system folders like /Applications/Utilities
|
||||
systemApps = systemApps.filter { !$0.absoluteString.contains("/Applications/Utilities/") }
|
||||
return (systemApps, userApps)
|
||||
// Get app info and sort
|
||||
let sortedApps = apps
|
||||
.compactMap { getAppInfo(atPath: $0) }
|
||||
.sorted { $0.appName.replacingOccurrences(of: ".", with: "").lowercased() < $1.appName.replacingOccurrences(of: ".", with: "").lowercased() }
|
||||
|
||||
return sortedApps
|
||||
}
|
||||
|
||||
|
||||
// Get app bundle information from provided path
|
||||
func getAppInfo(atPath path: URL) -> AppInfo? {
|
||||
func getAppInfo(atPath path: URL, wrapped: Bool = false) -> AppInfo? {
|
||||
let filemanager = FileManager.default
|
||||
let wrapperURL = path.appendingPathComponent("Wrapper")
|
||||
if filemanager.fileExists(atPath: wrapperURL.path) {
|
||||
do {
|
||||
let contents = try filemanager.contentsOfDirectory(at: wrapperURL, includingPropertiesForKeys: nil, options: [])
|
||||
let appFiles = contents.filter { $0.pathExtension == "app" }
|
||||
|
||||
if let firstAppFile = appFiles.first {
|
||||
let fullPath = wrapperURL.appendingPathComponent(firstAppFile.lastPathComponent)
|
||||
if let wrappedAppInfo = getAppInfo(atPath: fullPath, wrapped: true) {
|
||||
return wrappedAppInfo
|
||||
}
|
||||
} else {
|
||||
printOS("No .app files found in the 'Wrapper' directory: \(wrapperURL)")
|
||||
}
|
||||
} catch {
|
||||
printOS("Error reading contents of 'Wrapper' directory: \(error.localizedDescription)\n\(wrapperURL)")
|
||||
}
|
||||
} else {
|
||||
if let bundle = Bundle(url: path) {
|
||||
if let bundleIdentifier = bundle.bundleIdentifier,
|
||||
var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String {
|
||||
var appVersion = "0.0.0"
|
||||
if let bundleIdentifier = bundle.bundleIdentifier {
|
||||
var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String ?? ""
|
||||
var appVersion: String?
|
||||
var appIcon: NSImage?
|
||||
var appName: String?
|
||||
var webApp: Bool?
|
||||
var wrappedApp: Bool?
|
||||
var system: Bool?
|
||||
|
||||
|
||||
if let shortVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String {
|
||||
if let shortVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String, !shortVersion.isEmpty {
|
||||
appVersion = shortVersion
|
||||
} else {
|
||||
if let bundleVersion = bundle.infoDictionary?["CFBundleVersion"] as? String {
|
||||
if let bundleVersion = bundle.infoDictionary?["CFBundleVersion"] as? String, !bundleVersion.isEmpty {
|
||||
appVersion = bundleVersion
|
||||
} else {
|
||||
printOS("Failed to retrieve bundle version")
|
||||
@@ -96,6 +101,34 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
|
||||
} else {
|
||||
appName = path.deletingPathExtension().lastPathComponent
|
||||
}
|
||||
|
||||
if appIconFileName.isEmpty {
|
||||
if let iconsDict = bundle.infoDictionary?["CFBundleIcons"] as? [String: Any] {
|
||||
if let primaryIconDict = iconsDict["CFBundlePrimaryIcon"] as? [String: Any],
|
||||
let iconFiles = primaryIconDict["CFBundleIconFiles"] as? [String],
|
||||
let primaryIconFile = iconFiles.first {
|
||||
// Now, you can use the primaryIconFile to construct the path to the icon file
|
||||
let iconPath = path.appendingPathComponent(primaryIconFile)
|
||||
appIconFileName = iconPath.path
|
||||
|
||||
if let contents = try? FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil) {
|
||||
// Find the first file that matches the specified prefix
|
||||
if let foundURL = contents.first(where: { $0.lastPathComponent.hasPrefix(primaryIconFile) }),
|
||||
let image = NSImage(contentsOfFile: foundURL.path) {
|
||||
appIcon = image
|
||||
|
||||
|
||||
} else {
|
||||
printOS("No matching image found for \(primaryIconFile) in wrapped app.")
|
||||
}
|
||||
} else {
|
||||
printOS("Unable to access the directory at \(primaryIconFile) for wrapped app.")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the icon file name has an .icns extension, if not, add it
|
||||
if !appIconFileName.hasSuffix(".icns") {
|
||||
appIconFileName += ".icns"
|
||||
@@ -131,108 +164,27 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
|
||||
webApp = false
|
||||
}
|
||||
|
||||
if wrapped {
|
||||
wrappedApp = true
|
||||
} else {
|
||||
wrappedApp = false
|
||||
}
|
||||
|
||||
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName ?? "", appVersion: appVersion, appIcon: appIcon, webApp: webApp ?? false, wrapped: false, files: [], fileSize: [:], fileIcon: [:])
|
||||
if !path.path.contains(home) {
|
||||
system = true
|
||||
} else {
|
||||
system = false
|
||||
}
|
||||
|
||||
|
||||
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName ?? "", appVersion: appVersion ?? "", appIcon: appIcon, webApp: webApp ?? false, wrapped: wrappedApp ?? false, system: system ?? false, files: [], fileSize: [:], fileIcon: [:])
|
||||
|
||||
} else {
|
||||
let wrapperURL = path.appendingPathComponent("Wrapper")
|
||||
|
||||
// Check that file path exists, exit if not
|
||||
if FileManager.default.fileExists(atPath: wrapperURL.path) {
|
||||
do {
|
||||
let contents = try FileManager.default.contentsOfDirectory(at: wrapperURL, includingPropertiesForKeys: nil, options: [])
|
||||
let appFiles = contents.filter { $0.pathExtension == "app" }
|
||||
|
||||
if let firstAppFile = appFiles.first {
|
||||
let fullPath = wrapperURL.appendingPathComponent(firstAppFile.lastPathComponent)
|
||||
if let wrappedAppInfo = getWrappedAppInfo(atPath: fullPath) {
|
||||
return wrappedAppInfo
|
||||
}
|
||||
} else {
|
||||
printOS("No .app files found in the 'Wrapper' directory.")
|
||||
}
|
||||
} catch {
|
||||
printOS("Error reading contents of 'Wrapper' directory: \(error.localizedDescription)")
|
||||
}
|
||||
} else {
|
||||
printOS("Error: 'Wrapper' directory not found at path: \(wrapperURL.path)")
|
||||
}
|
||||
|
||||
printOS("Bundle identifier not found at path: \(path)")
|
||||
}
|
||||
} else {
|
||||
printOS("Bundle not found at path: \(path)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func getWrappedAppInfo(atPath path: URL) -> AppInfo? {
|
||||
if let bundle = Bundle(url: path) {
|
||||
if let bundleIdentifier = bundle.bundleIdentifier,
|
||||
let appVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String {
|
||||
var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String
|
||||
var appIcon: NSImage?
|
||||
var appName: String?
|
||||
var webApp: Bool?
|
||||
|
||||
if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String {
|
||||
appName = localizedName
|
||||
} else if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String {
|
||||
appName = bundleName
|
||||
} else {
|
||||
appName = path.deletingPathExtension().lastPathComponent
|
||||
}
|
||||
|
||||
if appIconFileName == nil {
|
||||
if let iconsDict = bundle.infoDictionary?["CFBundleIcons"] as? [String: Any] {
|
||||
if let primaryIconDict = iconsDict["CFBundlePrimaryIcon"] as? [String: Any],
|
||||
let iconFiles = primaryIconDict["CFBundleIconFiles"] as? [String],
|
||||
let primaryIconFile = iconFiles.first {
|
||||
// Now, you can use the primaryIconFile to construct the path to the icon file
|
||||
let iconPath = path.appendingPathComponent(primaryIconFile)
|
||||
appIconFileName = iconPath.path
|
||||
|
||||
if let contents = try? FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil) {
|
||||
// Find the first file that matches the specified prefix
|
||||
if let foundURL = contents.first(where: { $0.lastPathComponent.hasPrefix(primaryIconFile) }),
|
||||
let image = NSImage(contentsOfFile: foundURL.path) {
|
||||
appIcon = image
|
||||
|
||||
|
||||
} else {
|
||||
printOS("No matching image found for \(primaryIconFile).")
|
||||
}
|
||||
} else {
|
||||
printOS("Unable to access the directory at \(primaryIconFile).")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the icon to a 100x100 PNG image
|
||||
if let pngIcon = appIcon.flatMap({ convertICNSToPNG(icon: $0, size: NSSize(width: 100, height: 100)) }) {
|
||||
appIcon = pngIcon
|
||||
}
|
||||
|
||||
if appIcon == nil {
|
||||
printOS("App Icon not found for app at path: \(path)")
|
||||
}
|
||||
|
||||
if bundle.infoDictionary?["LSTemplateApplication"] is Bool {
|
||||
webApp = true
|
||||
} else {
|
||||
webApp = false
|
||||
}
|
||||
|
||||
|
||||
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName ?? "", appVersion: appVersion, appIcon: appIcon, webApp: webApp ?? false, wrapped: true, files: [], fileSize: [:], fileIcon: [:])
|
||||
|
||||
} else {
|
||||
printOS("One or more variables missing for app at path: \(path)")
|
||||
}
|
||||
} else {
|
||||
printOS("Bundle not found at path: \(path)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -487,6 +439,7 @@ func loadAllPaths(allApps: [AppInfo], appState: AppState, locations: Locations,
|
||||
}
|
||||
} else {
|
||||
printOS("loadAllPaths - Retry limit timed out. Unable to load all paths within 1 minute.")
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,8 +317,7 @@ struct AnimatedSearchStyle: TextFieldStyle {
|
||||
appState.reload.toggle()
|
||||
let sortedApps = getSortedApps()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
appState.sortedApps.userApps = sortedApps.userApps
|
||||
appState.sortedApps.systemApps = sortedApps.systemApps
|
||||
appState.sortedApps = sortedApps
|
||||
appState.reload.toggle()
|
||||
}
|
||||
}
|
||||
@@ -431,10 +430,15 @@ struct SimpleSearchStyle: TextFieldStyle {
|
||||
.onHover { hovering in
|
||||
withAnimation(Animation.easeInOut(duration: 0.15)) {
|
||||
self.isHovered = hovering
|
||||
self.isFocused = hovering
|
||||
self.isFocused = true
|
||||
}
|
||||
}
|
||||
.focused($isFocused)
|
||||
.onAppear {
|
||||
updateOnMain {
|
||||
self.isFocused = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,21 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import OSLog
|
||||
import CoreImage
|
||||
import AppKit
|
||||
|
||||
// Make updates on main thread
|
||||
func updateOnMain(_ updates: @escaping () -> Void) {
|
||||
func updateOnMain(after delay: Double? = nil, _ updates: @escaping () -> Void) {
|
||||
if let delay = delay {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
updates()
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
updates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Execute functions on background thread
|
||||
@@ -170,7 +178,7 @@ func openTrash() {
|
||||
|
||||
// Check if restricted app
|
||||
func isRestricted(atPath path: URL) -> Bool {
|
||||
if path.path.contains("Safari") || path.path.contains(Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String ?? "") {
|
||||
if path.path.contains("Safari") || path.path.contains(Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String ?? "") || path.path.contains("/Applications/Utilities") {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -209,6 +217,48 @@ func getIconForFileOrFolderNS(atPath path: URL) -> NSImage? {
|
||||
return nsImage
|
||||
}
|
||||
|
||||
// Get average color from image
|
||||
//extension NSImage {
|
||||
// var averageColor2: NSColor? {
|
||||
// guard let inputImage = CIImage(image: self) else { return nil }
|
||||
// let extentVector = CIVector(x: inputImage.extent.origin.x, y: inputImage.extent.origin.y, z: inputImage.extent.size.width, w: inputImage.extent.size.height)
|
||||
//
|
||||
// guard let filter = CIFilter(name: "CIAreaAverage", parameters: [kCIInputImageKey: inputImage, kCIInputExtentKey: extentVector]) else { return nil }
|
||||
// guard let outputImage = filter.outputImage else { return nil }
|
||||
//
|
||||
// var bitmap = [UInt8](repeating: 0, count: 4)
|
||||
// let context = CIContext(options: [.workingColorSpace: kCFNull!])
|
||||
// context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil)
|
||||
//
|
||||
// return NSColor(red: CGFloat(bitmap[0]) / 255, green: CGFloat(bitmap[1]) / 255, blue: CGFloat(bitmap[2]) / 255, alpha: CGFloat(bitmap[3]) / 255)
|
||||
// }
|
||||
//}
|
||||
|
||||
extension NSImage {
|
||||
var averageColor: NSColor? {
|
||||
guard let tiffData = self.tiffRepresentation, let bitmapImage = NSBitmapImageRep(data: tiffData), let inputImage = CIImage(bitmapImageRep: bitmapImage) else { return nil }
|
||||
|
||||
let extentVector = CIVector(x: inputImage.extent.origin.x, y: inputImage.extent.origin.y, z: inputImage.extent.size.width, w: inputImage.extent.size.height)
|
||||
|
||||
guard let filter = CIFilter(name: "CIAreaAverage", parameters: [kCIInputImageKey: inputImage, kCIInputExtentKey: extentVector]) else { return nil }
|
||||
guard let outputImage = filter.outputImage else { return nil }
|
||||
|
||||
var bitmap = [UInt8](repeating: 0, count: 4)
|
||||
let context = CIContext(options: [.workingColorSpace: NSNull()])
|
||||
context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: CIFormat.RGBA8, colorSpace: nil)
|
||||
|
||||
return NSColor(red: CGFloat(bitmap[0]) / 255, green: CGFloat(bitmap[1]) / 255, blue: CGFloat(bitmap[2]) / 255, alpha: CGFloat(bitmap[3]) / 255)
|
||||
}
|
||||
}
|
||||
|
||||
func startEnd(_ function: @escaping () -> Void) {
|
||||
let startTime = Date() // Capture start time
|
||||
function()
|
||||
let endTime = Date()
|
||||
let executionTime = endTime.timeIntervalSince(startTime)
|
||||
print("Function executed in: \n\(executionTime) seconds")
|
||||
}
|
||||
|
||||
|
||||
// Relaunch app
|
||||
func relaunchApp(afterDelay seconds: TimeInterval = 0.5) -> Never {
|
||||
@@ -221,6 +271,25 @@ func relaunchApp(afterDelay seconds: TimeInterval = 0.5) -> Never {
|
||||
exit(0)
|
||||
}
|
||||
|
||||
// Remove app from cache
|
||||
func removeApp(appState: AppState, withId id: UUID) {
|
||||
DispatchQueue.main.async {
|
||||
// Remove from sortedApps if found
|
||||
if let index = appState.sortedApps.firstIndex(where: { $0.id == id }) {
|
||||
appState.sortedApps.remove(at: index)
|
||||
return // Exit the function if the app was found and removed
|
||||
}
|
||||
|
||||
// Remove from appInfoStore if found
|
||||
if let index = appState.appInfoStore.firstIndex(where: { $0.id == id }) {
|
||||
appState.appInfoStore.remove(at: index)
|
||||
}
|
||||
// Clear out AppInfo state
|
||||
appState.appInfo = AppInfo.empty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Extend Int to convert hours to seconds ---
|
||||
extension Int {
|
||||
|
||||
@@ -33,7 +33,7 @@ struct PearcleanerApp: App {
|
||||
Group {
|
||||
ZStack() {
|
||||
if !mini {
|
||||
AppListView(search: $search, showPopover: $showPopover)
|
||||
RegularMode(search: $search, showPopover: $showPopover)
|
||||
.environmentObject(locations)
|
||||
} else {
|
||||
MiniMode(search: $search, showPopover: $showPopover)
|
||||
@@ -91,13 +91,14 @@ struct PearcleanerApp: App {
|
||||
|
||||
// Get Apps
|
||||
let sortedApps = getSortedApps()
|
||||
appState.sortedApps.userApps = sortedApps.userApps
|
||||
appState.sortedApps.systemApps = sortedApps.systemApps
|
||||
appState.sortedApps = sortedApps
|
||||
|
||||
|
||||
// Find all app paths on load
|
||||
if instantSearch {
|
||||
loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations)
|
||||
// startEnd {
|
||||
loadAllPaths(allApps: sortedApps, appState: appState, locations: locations)
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,13 +32,5 @@
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>SUPublicEDKey</key>
|
||||
<string>cD156JWtfwtgFJH2RApyj9Bgc0fCqLBGEf9C9IRMCWE=</string>
|
||||
<key>SUFeedURL</key>
|
||||
<string>https://raw.githubusercontent.com/alienator88/Pearcleaner/main/appcast.xml</string>
|
||||
<key>SUEnableInstallerLauncherService</key>
|
||||
<true/>
|
||||
<key>SUEnableDownloaderService</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -16,6 +16,7 @@ struct AppListItems: View {
|
||||
@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
|
||||
@Binding var showPopover: Bool
|
||||
@EnvironmentObject var locations: Locations
|
||||
let itemId = UUID()
|
||||
@@ -25,7 +26,7 @@ struct AppListItems: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
// VStack(alignment: .leading, spacing: 5) {
|
||||
|
||||
|
||||
|
||||
@@ -39,11 +40,27 @@ 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())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
@@ -84,8 +101,6 @@ struct AppListItems: View {
|
||||
.foregroundStyle(Color("mode").opacity(0.5))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.horizontal, 5)
|
||||
.help(appInfo.appName)
|
||||
.onHover { hovering in
|
||||
@@ -94,7 +109,22 @@ struct AppListItems: View {
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
withAnimation(Animation.easeInOut(duration: 0.4)) {
|
||||
showAppInFiles(appInfo: appInfo, mini: mini, appState: appState, locations: locations, showPopover: $showPopover)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func opacityForItem() -> 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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,17 +11,19 @@ import SwiftUI
|
||||
struct AppsListView: View {
|
||||
@Binding var search: String
|
||||
@Binding var showPopover: Bool
|
||||
var filteredUserApps: [AppInfo]
|
||||
var filteredSystemApps: [AppInfo]
|
||||
var filteredApps: [AppInfo]
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) {
|
||||
if filteredUserApps.count > 0 {
|
||||
let filteredUserApps = filteredApps.filter { !$0.system }
|
||||
let filteredSystemApps = filteredApps.filter { $0.system }
|
||||
|
||||
if !filteredUserApps.isEmpty {
|
||||
SectionView(title: "User", count: filteredUserApps.count, apps: filteredUserApps, search: $search, showPopover: $showPopover)
|
||||
}
|
||||
|
||||
if filteredSystemApps.count > 0 {
|
||||
if !filteredSystemApps.isEmpty {
|
||||
SectionView(title: "System", count: filteredSystemApps.count, apps: filteredSystemApps, search: $search, showPopover: $showPopover)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ struct FilesView: View {
|
||||
.padding([.horizontal, .top], 5)
|
||||
}
|
||||
VStack(spacing: 0) {
|
||||
|
||||
// Main Group
|
||||
HStack(alignment: .center) {
|
||||
|
||||
@@ -93,8 +94,6 @@ struct FilesView: View {
|
||||
|
||||
}
|
||||
|
||||
|
||||
if appState.appInfo.webApp || appState.appInfo.wrapped {
|
||||
HStack(alignment: .center, spacing: 10) {
|
||||
|
||||
Spacer()
|
||||
@@ -121,15 +120,21 @@ struct FilesView: View {
|
||||
|
||||
}
|
||||
|
||||
Text(appState.appInfo.system ? "system" : "user")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(Color("mode").opacity(0.5))
|
||||
.frame(minWidth: 30, minHeight: 15)
|
||||
.padding(2)
|
||||
.padding(.horizontal, 2)
|
||||
.background(Color("mode").opacity(0.1))
|
||||
.clipShape(.capsule)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 0)
|
||||
|
||||
}
|
||||
|
||||
Divider()
|
||||
@@ -184,7 +189,7 @@ struct FilesView: View {
|
||||
Button("Uninstall") {
|
||||
Task {
|
||||
updateOnMain {
|
||||
appState.appInfo = AppInfo.empty
|
||||
// appState.appInfo = AppInfo.empty
|
||||
search = ""
|
||||
if mini {
|
||||
appState.currentView = .apps
|
||||
@@ -214,7 +219,9 @@ struct FilesView: View {
|
||||
appState.isReminderVisible.toggle()
|
||||
}
|
||||
}
|
||||
refreshAppList(appState.appInfo)
|
||||
removeApp(appState: appState, withId: appState.appInfo.id)
|
||||
|
||||
// refreshAppList(appState.appInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +250,7 @@ struct FilesView: View {
|
||||
}
|
||||
.transition(.opacity)
|
||||
.padding([.horizontal, .bottom], 20)
|
||||
.padding(.top, !mini ? 10 : 0)
|
||||
|
||||
}
|
||||
|
||||
@@ -253,8 +261,8 @@ struct FilesView: View {
|
||||
showPopover = false
|
||||
let sortedApps = getSortedApps()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
appState.sortedApps.userApps = sortedApps.userApps
|
||||
appState.sortedApps.systemApps = sortedApps.systemApps
|
||||
appState.sortedApps = sortedApps
|
||||
// appState.sortedApps.systemApps = sortedApps.systemApps
|
||||
// if instantSearch {
|
||||
// loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations)
|
||||
// }
|
||||
|
||||
@@ -25,6 +25,7 @@ struct MiniMode: View {
|
||||
|
||||
// Main Mini View
|
||||
VStack(spacing: 0) {
|
||||
Group {
|
||||
if appState.currentView == .empty {
|
||||
TopBarMini(search: $search, showPopover: $showPopover)
|
||||
MiniEmptyView(showPopover: $showPopover)
|
||||
@@ -32,9 +33,9 @@ struct MiniMode: View {
|
||||
TopBarMini(search: $search, showPopover: $showPopover)
|
||||
MiniAppView(search: $search, showPopover: $showPopover)
|
||||
}
|
||||
|
||||
}
|
||||
.transition(.move(edge: .leading))
|
||||
.transition(.opacity)
|
||||
}
|
||||
.popover(isPresented: $showPopover, arrowEdge: .trailing) {
|
||||
VStack {
|
||||
if appState.currentView == .files {
|
||||
@@ -149,19 +150,11 @@ struct MiniAppView: View {
|
||||
|
||||
var body: some View {
|
||||
|
||||
var filteredUserApps: [AppInfo] {
|
||||
var filteredApps: [AppInfo] {
|
||||
if search.isEmpty {
|
||||
return appState.sortedApps.userApps
|
||||
return appState.sortedApps
|
||||
} else {
|
||||
return appState.sortedApps.userApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
|
||||
}
|
||||
}
|
||||
|
||||
var filteredSystemApps: [AppInfo] {
|
||||
if search.isEmpty {
|
||||
return appState.sortedApps.systemApps
|
||||
} else {
|
||||
return appState.sortedApps.systemApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
|
||||
return appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +179,7 @@ struct MiniAppView: View {
|
||||
} else {
|
||||
VStack(alignment: .center) {
|
||||
|
||||
AppsListView(search: $search, showPopover: $showPopover, filteredUserApps: filteredUserApps, filteredSystemApps: filteredSystemApps)
|
||||
AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps)
|
||||
|
||||
if appState.currentView != .empty {
|
||||
SearchBarMiniBottom(search: $search).background(.ultraThinMaterial)
|
||||
@@ -198,7 +191,7 @@ struct MiniAppView: View {
|
||||
|
||||
}
|
||||
}
|
||||
.transition(.move(edge: .leading))
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct AppListView: View {
|
||||
struct RegularMode: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@AppStorage("settings.general.glass") private var glass: Bool = false
|
||||
@AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280
|
||||
@@ -17,22 +17,18 @@ struct AppListView: View {
|
||||
@State private var showSys: Bool = true
|
||||
@State private var showUsr: Bool = true
|
||||
@Binding var showPopover: Bool
|
||||
// @State private var isHovering = false
|
||||
// @State private var isHovering2 = false
|
||||
|
||||
var filteredUserApps: [AppInfo] {
|
||||
|
||||
var filteredApps: [AppInfo] {
|
||||
if search.isEmpty {
|
||||
return appState.sortedApps.userApps
|
||||
return appState.sortedApps
|
||||
} else {
|
||||
return appState.sortedApps.userApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
|
||||
return appState.sortedApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
|
||||
}
|
||||
}
|
||||
|
||||
var filteredSystemApps: [AppInfo] {
|
||||
if search.isEmpty {
|
||||
return appState.sortedApps.systemApps
|
||||
} else {
|
||||
return appState.sortedApps.systemApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -66,23 +62,31 @@ struct AppListView: View {
|
||||
.padding(.top, 20)
|
||||
|
||||
|
||||
AppsListView(search: $search, showPopover: $showPopover, filteredUserApps: filteredUserApps, filteredSystemApps: filteredSystemApps)
|
||||
AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps)
|
||||
|
||||
}
|
||||
.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)
|
||||
.transition(.move(edge: .leading))
|
||||
.transition(.opacity)
|
||||
|
||||
SlideableDivider(dimension: $sidebarWidth)
|
||||
|
||||
|
||||
// Details View
|
||||
VStack(spacing: 0) {
|
||||
Group {
|
||||
if appState.currentView == .empty || appState.currentView == .apps {
|
||||
TopBar(showPopover: $showPopover)
|
||||
AppDetailsEmptyView(showPopover: $showPopover)
|
||||
@@ -96,7 +100,16 @@ struct AppListView: View {
|
||||
.id(appState.appInfo.id)
|
||||
}
|
||||
}
|
||||
.transition(.move(edge: .leading))
|
||||
.transition(.opacity)
|
||||
}
|
||||
// .onHover { hovering in
|
||||
// withAnimation(Animation.easeInOut(duration: 0.4)) {
|
||||
// isHovering = hovering
|
||||
// }
|
||||
// }
|
||||
// .opacity((isHovering2 || (!isHovering && !isHovering2)) ? 1 : 0.5)
|
||||
|
||||
|
||||
}
|
||||
.frame(minWidth: 900, minHeight: 600)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
@@ -187,10 +200,9 @@ struct Header: View {
|
||||
showPopover = false
|
||||
let sortedApps = getSortedApps()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
appState.sortedApps.userApps = sortedApps.userApps
|
||||
appState.sortedApps.systemApps = sortedApps.systemApps
|
||||
appState.sortedApps = sortedApps
|
||||
if instantSearch {
|
||||
loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) {
|
||||
loadAllPaths(allApps: sortedApps, appState: appState, locations: locations) {
|
||||
appState.reload.toggle()
|
||||
|
||||
}
|
||||
@@ -53,7 +53,7 @@ struct TopBar: View {
|
||||
if instantSearch {
|
||||
reversePathsSearch(appState: appState, locations: locations)
|
||||
} else {
|
||||
loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true)
|
||||
loadAllPaths(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true)
|
||||
}
|
||||
} else {
|
||||
appState.currentView = .zombie
|
||||
|
||||
@@ -39,7 +39,7 @@ struct TopBarMini: View {
|
||||
if instantSearch {
|
||||
reversePathsSearch(appState: appState, locations: locations)
|
||||
} else {
|
||||
loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true)
|
||||
loadAllPaths(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true)
|
||||
}
|
||||
} else {
|
||||
appState.currentView = .zombie
|
||||
@@ -78,7 +78,7 @@ struct TopBarMini: View {
|
||||
if instantSearch {
|
||||
reversePathsSearch(appState: appState, locations: locations)
|
||||
} else {
|
||||
loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true)
|
||||
loadAllPaths(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true)
|
||||
}
|
||||
} else {
|
||||
appState.currentView = .zombie
|
||||
|
||||
@@ -24,6 +24,7 @@ struct ZombieView: View {
|
||||
VStack(alignment: .center) {
|
||||
if appState.showProgress {
|
||||
VStack {
|
||||
Group {
|
||||
Spacer()
|
||||
|
||||
Text("Searching the file system").font(.title3)
|
||||
@@ -34,8 +35,10 @@ struct ZombieView: View {
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.transition(.opacity)
|
||||
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
// Titlebar
|
||||
if mini {
|
||||
@@ -49,7 +52,7 @@ struct ZombieView: View {
|
||||
if instantSearch {
|
||||
reversePathsSearch(appState: appState, locations: locations)
|
||||
} else {
|
||||
loadAllPaths(allApps: appState.sortedApps.userApps + appState.sortedApps.systemApps, appState: appState, locations: locations, reverseAddon: true)
|
||||
loadAllPaths(allApps: appState.sortedApps, appState: appState, locations: locations, reverseAddon: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,6 +209,7 @@ struct ZombieView: View {
|
||||
}
|
||||
.transition(.opacity)
|
||||
.padding([.horizontal, .bottom], 20)
|
||||
.padding(.top, !mini ? 10 : 0)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,14 @@ struct UpdateView: View {
|
||||
VStack(spacing: 5) {
|
||||
HStack {
|
||||
|
||||
Text("App: \(Bundle.main.version)")
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
.opacity(0.5)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
.frame(width: appState.progressBar.1 != 1.0 ? 150 : 180)
|
||||
// .frame(width: appState.progressBar.1 != 1.0 ? 150 : 180)
|
||||
|
||||
Text("\(appState.progressBar.1 != 1.0 ? "Update Available 🥳" : "Completed 🚀")")
|
||||
.font(.title)
|
||||
@@ -26,7 +32,7 @@ struct UpdateView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("v\(appState.releases.first?.tag_name ?? "")")
|
||||
Text("GitHub: \(appState.releases.first?.tag_name ?? "")")
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
.opacity(0.5)
|
||||
@@ -107,17 +113,23 @@ struct NoUpdateView: View {
|
||||
VStack(spacing: 5) {
|
||||
HStack {
|
||||
|
||||
Spacer()
|
||||
.frame(width: appState.progressBar.1 != 1.0 ? 140 : 180)
|
||||
Text("App: \(Bundle.main.version)")
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
.opacity(0.5)
|
||||
.padding()
|
||||
|
||||
Text("\(appState.progressBar.1 != 1.0 ? "No Update Available 😌" : "Completed 🚀")")
|
||||
Spacer()
|
||||
// .frame(width: appState.progressBar.1 != 1.0 ? 140 : 180)
|
||||
|
||||
Text("\(appState.progressBar.1 != 1.0 ? "No Update 😌" : "Completed 🚀")")
|
||||
.font(.title)
|
||||
.bold()
|
||||
.padding(.vertical)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("v\(appState.releases.first?.tag_name ?? "")")
|
||||
Text("GitHub: \(appState.releases.first?.tag_name ?? "")")
|
||||
.font(.title3)
|
||||
.fontWeight(.semibold)
|
||||
.opacity(0.5)
|
||||
@@ -125,6 +137,12 @@ struct NoUpdateView: View {
|
||||
|
||||
}
|
||||
|
||||
// Text("App: \(Bundle.main.version) | GitHub: \(appState.releases.first?.tag_name ?? "")")
|
||||
// .font(.title3)
|
||||
// .fontWeight(.semibold)
|
||||
// .opacity(0.5)
|
||||
// .padding(.vertical, 5)
|
||||
|
||||
Divider()
|
||||
.padding([.horizontal])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user