This commit is contained in:
Alin
2024-03-08 17:55:38 -07:00
parent 90deaf023c
commit bc8d4d22f6
17 changed files with 399 additions and 312 deletions
+4 -4
View File
@@ -19,7 +19,7 @@
C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; }; C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; };
C76D084A2AF83C6E00D07867 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08492AF83C6E00D07867 /* General.swift */; }; C76D084A2AF83C6E00D07867 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08492AF83C6E00D07867 /* General.swift */; };
C76D08512AF850F300D07867 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08502AF850F300D07867 /* About.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 */; }; C77B90042AF18E2E009CC655 /* PearcleanerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90032AF18E2E009CC655 /* PearcleanerApp.swift */; };
C77B90082AF18E2F009CC655 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C77B90072AF18E2F009CC655 /* Assets.xcassets */; }; C77B90082AF18E2F009CC655 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C77B90072AF18E2F009CC655 /* Assets.xcassets */; };
C77B90162AF19377009CC655 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90152AF19377009CC655 /* AppState.swift */; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; C77B90072AF18E2F009CC655 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -219,7 +219,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
C77B90222AF2D616009CC655 /* FilesView.swift */, C77B90222AF2D616009CC655 /* FilesView.swift */,
C76D08542AF89CDE00D07867 /* AppListView.swift */, C76D08542AF89CDE00D07867 /* RegularMode.swift */,
C7D31D472AFEB23700C7ED9E /* TopBar.swift */, C7D31D472AFEB23700C7ED9E /* TopBar.swift */,
C7045A292B03FAF000376976 /* TopBarMini.swift */, C7045A292B03FAF000376976 /* TopBarMini.swift */,
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */, C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
@@ -360,7 +360,7 @@
C7D31D482AFEB23700C7ED9E /* TopBar.swift in Sources */, C7D31D482AFEB23700C7ED9E /* TopBar.swift in Sources */,
C7ACADE62B92A737000B5845 /* Features.swift in Sources */, C7ACADE62B92A737000B5845 /* Features.swift in Sources */,
C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */, C7F539382AF60865007DF1B2 /* Utilities.swift in Sources */,
C76D08552AF89CDE00D07867 /* AppListView.swift in Sources */, C76D08552AF89CDE00D07867 /* RegularMode.swift in Sources */,
C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */, C7A9CE472B89164700EB6E78 /* Authorization.swift in Sources */,
C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */, C7CF47242B3B3F1700979C5F /* WindowSettings.swift in Sources */,
C77B90232AF2D616009CC655 /* FilesView.swift in Sources */, C77B90232AF2D616009CC655 /* FilesView.swift in Sources */,
+6 -7
View File
@@ -38,13 +38,13 @@ struct AppCommands: Commands {
} }
let sortedApps = getSortedApps() let sortedApps = getSortedApps()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.sortedApps.userApps = [] appState.sortedApps = []
appState.sortedApps.systemApps = [] // appState.sortedApps.systemApps = []
appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps = sortedApps
appState.sortedApps.systemApps = sortedApps.systemApps // appState.sortedApps.systemApps = sortedApps.systemApps
if instantSearch { if instantSearch {
Task(priority: .high){ Task(priority: .high){
loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) loadAllPaths(allApps: sortedApps, appState: appState, locations: locations)
} }
} }
appState.reload.toggle() appState.reload.toggle()
@@ -73,8 +73,7 @@ struct AppCommands: Commands {
undoTrash(appState: appState) { undoTrash(appState: appState) {
let sortedApps = getSortedApps() let sortedApps = getSortedApps()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps = sortedApps
appState.sortedApps.systemApps = sortedApps.systemApps
// if instantSearch { // if instantSearch {
// loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) // loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations)
// } // }
+4 -2
View File
@@ -15,7 +15,7 @@ class AppState: ObservableObject
@Published var appInfo: AppInfo @Published var appInfo: AppInfo
@Published var appInfoStore: [AppInfo] = [] @Published var appInfoStore: [AppInfo] = []
@Published var zombieFile: ZombieFile @Published var zombieFile: ZombieFile
@Published var sortedApps: (userApps: [AppInfo], systemApps: [AppInfo]) = ([], []) @Published var sortedApps: [AppInfo] = []
@Published var selectedItems = Set<URL>() @Published var selectedItems = Set<URL>()
@Published var selectedZombieItems = Set<URL>() @Published var selectedZombieItems = Set<URL>()
@Published var alertType = AlertType.off @Published var alertType = AlertType.off
@@ -41,6 +41,7 @@ class AppState: ObservableObject
appIcon: nil, appIcon: nil,
webApp: false, webApp: false,
wrapped: false, wrapped: false,
system: false,
files: [], files: [],
fileSize: [:], fileSize: [:],
fileIcon: [:] fileIcon: [:]
@@ -68,6 +69,7 @@ struct AppInfo: Identifiable, Equatable, Hashable {
let appIcon: NSImage? let appIcon: NSImage?
let webApp: Bool let webApp: Bool
let wrapped: Bool let wrapped: Bool
let system: Bool
var files: [URL] var files: [URL]
var fileSize: [URL:Int64] var fileSize: [URL:Int64]
var fileIcon: [URL:NSImage?] 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
View File
@@ -8,46 +8,27 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
// Get list of apps and sort it // Get all apps from /Applications and ~/Applications
func getSortedApps() -> (systemApps: [AppInfo], userApps: [AppInfo]) { func getSortedApps() -> [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]) {
let fileManager = FileManager.default let fileManager = FileManager.default
// Define the paths for system and user applications // Define the paths for system and user applications
let systemAppsPath = "/Applications" let systemAppsPath = "/Applications"
let userAppsPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Applications").path let userAppsPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Applications").path
var systemApps: [URL] = [] var apps: [URL] = []
var userApps: [URL] = []
func collectAppPaths(at directoryPath: String, isSystem: Bool) { func collectAppPaths(at directoryPath: String) {
do { do {
let appURLs = try fileManager.contentsOfDirectory(at: URL(fileURLWithPath: directoryPath), includingPropertiesForKeys: nil, options: []) let appURLs = try fileManager.contentsOfDirectory(at: URL(fileURLWithPath: directoryPath), includingPropertiesForKeys: nil, options: [])
for appURL in appURLs { for appURL in appURLs {
if appURL.pathExtension == "app" { if appURL.pathExtension == "app" && !isRestricted(atPath: appURL) {
// Add the path to the appropriate list // Add the path to the array
if isSystem && !isRestricted(atPath: appURL) { apps.append(appURL)
systemApps.append(appURL)
} else if !isSystem && !isRestricted(atPath: appURL) {
userApps.append(appURL)
}
} else if appURL.hasDirectoryPath { } else if appURL.hasDirectoryPath {
// If it's a directory, recursively explore it // If it's a directory, recursively explore it
collectAppPaths(at: appURL.path, isSystem: isSystem) collectAppPaths(at: appURL.path)
} }
} }
} catch { } catch {
@@ -56,33 +37,57 @@ func getApplications() -> (systemApps: [URL], userApps: [URL]) {
} }
} }
// Collect system applications // Collect system applications
collectAppPaths(at: systemAppsPath, isSystem: true) collectAppPaths(at: systemAppsPath)
// Collect user applications // Collect user applications
collectAppPaths(at: userAppsPath, isSystem: false) collectAppPaths(at: userAppsPath)
// Filter out unwanted system folders like /Applications/Utilities // Get app info and sort
systemApps = systemApps.filter { !$0.absoluteString.contains("/Applications/Utilities/") } let sortedApps = apps
return (systemApps, userApps) .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 // 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 bundle = Bundle(url: path) {
if let bundleIdentifier = bundle.bundleIdentifier, if let bundleIdentifier = bundle.bundleIdentifier {
var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String { var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String ?? ""
var appVersion = "0.0.0" var appVersion: String?
var appIcon: NSImage? var appIcon: NSImage?
var appName: String? var appName: String?
var webApp: Bool? var webApp: Bool?
var wrappedApp: Bool?
var system: Bool?
if let shortVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String, !shortVersion.isEmpty {
if let shortVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String {
appVersion = shortVersion appVersion = shortVersion
} else { } else {
if let bundleVersion = bundle.infoDictionary?["CFBundleVersion"] as? String { if let bundleVersion = bundle.infoDictionary?["CFBundleVersion"] as? String, !bundleVersion.isEmpty {
appVersion = bundleVersion appVersion = bundleVersion
} else { } else {
printOS("Failed to retrieve bundle version") printOS("Failed to retrieve bundle version")
@@ -96,6 +101,34 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
} else { } else {
appName = path.deletingPathExtension().lastPathComponent 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 // Check if the icon file name has an .icns extension, if not, add it
if !appIconFileName.hasSuffix(".icns") { if !appIconFileName.hasSuffix(".icns") {
appIconFileName += ".icns" appIconFileName += ".icns"
@@ -131,108 +164,27 @@ func getAppInfo(atPath path: URL) -> AppInfo? {
webApp = false 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 { } else {
let wrapperURL = path.appendingPathComponent("Wrapper") printOS("Bundle identifier not found at path: \(path)")
// 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)")
}
} }
} else { } else {
printOS("Bundle not found at path: \(path)") 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 return nil
} }
@@ -487,6 +439,7 @@ func loadAllPaths(allApps: [AppInfo], appState: AppState, locations: Locations,
} }
} else { } else {
printOS("loadAllPaths - Retry limit timed out. Unable to load all paths within 1 minute.") printOS("loadAllPaths - Retry limit timed out. Unable to load all paths within 1 minute.")
completion()
} }
} }
} }
+7 -3
View File
@@ -317,8 +317,7 @@ struct AnimatedSearchStyle: TextFieldStyle {
appState.reload.toggle() appState.reload.toggle()
let sortedApps = getSortedApps() let sortedApps = getSortedApps()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps = sortedApps
appState.sortedApps.systemApps = sortedApps.systemApps
appState.reload.toggle() appState.reload.toggle()
} }
} }
@@ -431,10 +430,15 @@ struct SimpleSearchStyle: TextFieldStyle {
.onHover { hovering in .onHover { hovering in
withAnimation(Animation.easeInOut(duration: 0.15)) { withAnimation(Animation.easeInOut(duration: 0.15)) {
self.isHovered = hovering self.isHovered = hovering
self.isFocused = hovering self.isFocused = true
} }
} }
.focused($isFocused) .focused($isFocused)
.onAppear {
updateOnMain {
self.isFocused = false
}
}
} }
} }
+71 -2
View File
@@ -8,13 +8,21 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
import OSLog import OSLog
import CoreImage
import AppKit
// Make updates on main thread // 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 { DispatchQueue.main.async {
updates() updates()
} }
} }
}
// Execute functions on background thread // Execute functions on background thread
@@ -170,7 +178,7 @@ func openTrash() {
// Check if restricted app // Check if restricted app
func isRestricted(atPath path: URL) -> Bool { 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 return true
} else { } else {
return false return false
@@ -209,6 +217,48 @@ func getIconForFileOrFolderNS(atPath path: URL) -> NSImage? {
return 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 // Relaunch app
func relaunchApp(afterDelay seconds: TimeInterval = 0.5) -> Never { func relaunchApp(afterDelay seconds: TimeInterval = 0.5) -> Never {
@@ -221,6 +271,25 @@ func relaunchApp(afterDelay seconds: TimeInterval = 0.5) -> Never {
exit(0) 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 --- // --- Extend Int to convert hours to seconds ---
extension Int { extension Int {
+5 -4
View File
@@ -33,7 +33,7 @@ struct PearcleanerApp: App {
Group { Group {
ZStack() { ZStack() {
if !mini { if !mini {
AppListView(search: $search, showPopover: $showPopover) RegularMode(search: $search, showPopover: $showPopover)
.environmentObject(locations) .environmentObject(locations)
} else { } else {
MiniMode(search: $search, showPopover: $showPopover) MiniMode(search: $search, showPopover: $showPopover)
@@ -91,13 +91,14 @@ struct PearcleanerApp: App {
// Get Apps // Get Apps
let sortedApps = getSortedApps() let sortedApps = getSortedApps()
appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps = sortedApps
appState.sortedApps.systemApps = sortedApps.systemApps
// Find all app paths on load // Find all app paths on load
if instantSearch { if instantSearch {
loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) // startEnd {
loadAllPaths(allApps: sortedApps, appState: appState, locations: locations)
// }
} }
-8
View File
@@ -32,13 +32,5 @@
</array> </array>
</dict> </dict>
</array> </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> </dict>
</plist> </plist>
+33 -3
View File
@@ -16,6 +16,7 @@ struct AppListItems: View {
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@AppStorage("settings.general.mini") private var mini: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false
@AppStorage("settings.general.popover") private var popoverStay: Bool = true @AppStorage("settings.general.popover") private var popoverStay: Bool = true
@AppStorage("displayMode") var displayMode: DisplayMode = .system
@Binding var showPopover: Bool @Binding var showPopover: Bool
@EnvironmentObject var locations: Locations @EnvironmentObject var locations: Locations
let itemId = UUID() let itemId = UUID()
@@ -25,7 +26,7 @@ struct AppListItems: View {
} }
var body: some 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 { 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) Image(nsImage: appIcon)
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
// .scaledToFit()
.frame(width: 30, height: 30) .frame(width: 30, height: 30)
.clipShape(RoundedRectangle(cornerRadius: 8)) .clipShape(RoundedRectangle(cornerRadius: 8))
.shadow(color: .black.opacity(0.5), radius: 2, y: 2)
// .grayscale(opacityForItem())
}
} }
VStack(alignment: .leading, spacing: 5) { VStack(alignment: .leading, spacing: 5) {
@@ -84,8 +101,6 @@ struct AppListItems: View {
.foregroundStyle(Color("mode").opacity(0.5)) .foregroundStyle(Color("mode").opacity(0.5))
} }
}
.padding(.horizontal, 5) .padding(.horizontal, 5)
.help(appInfo.appName) .help(appInfo.appName)
.onHover { hovering in .onHover { hovering in
@@ -94,7 +109,22 @@ struct AppListItems: View {
} }
} }
.onTapGesture { .onTapGesture {
withAnimation(Animation.easeInOut(duration: 0.4)) {
showAppInFiles(appInfo: appInfo, mini: mini, appState: appState, locations: locations, showPopover: $showPopover) 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
}
}
+6 -4
View File
@@ -11,17 +11,19 @@ import SwiftUI
struct AppsListView: View { struct AppsListView: View {
@Binding var search: String @Binding var search: String
@Binding var showPopover: Bool @Binding var showPopover: Bool
var filteredUserApps: [AppInfo] var filteredApps: [AppInfo]
var filteredSystemApps: [AppInfo]
var body: some View { var body: some View {
ScrollView { ScrollView {
LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) { 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) 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) SectionView(title: "System", count: filteredSystemApps.count, apps: filteredSystemApps, search: $search, showPopover: $showPopover)
} }
} }
+18 -10
View File
@@ -54,6 +54,7 @@ struct FilesView: View {
.padding([.horizontal, .top], 5) .padding([.horizontal, .top], 5)
} }
VStack(spacing: 0) { VStack(spacing: 0) {
// Main Group // Main Group
HStack(alignment: .center) { HStack(alignment: .center) {
@@ -93,8 +94,6 @@ struct FilesView: View {
} }
if appState.appInfo.webApp || appState.appInfo.wrapped {
HStack(alignment: .center, spacing: 10) { HStack(alignment: .center, spacing: 10) {
Spacer() 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(.horizontal, 20)
.padding(.top, 0) .padding(.top, 0)
} }
Divider() Divider()
@@ -184,7 +189,7 @@ struct FilesView: View {
Button("Uninstall") { Button("Uninstall") {
Task { Task {
updateOnMain { updateOnMain {
appState.appInfo = AppInfo.empty // appState.appInfo = AppInfo.empty
search = "" search = ""
if mini { if mini {
appState.currentView = .apps appState.currentView = .apps
@@ -214,7 +219,9 @@ struct FilesView: View {
appState.isReminderVisible.toggle() 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) .transition(.opacity)
.padding([.horizontal, .bottom], 20) .padding([.horizontal, .bottom], 20)
.padding(.top, !mini ? 10 : 0)
} }
@@ -253,8 +261,8 @@ struct FilesView: View {
showPopover = false showPopover = false
let sortedApps = getSortedApps() let sortedApps = getSortedApps()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps = sortedApps
appState.sortedApps.systemApps = sortedApps.systemApps // appState.sortedApps.systemApps = sortedApps.systemApps
// if instantSearch { // if instantSearch {
// loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) // loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations)
// } // }
+8 -15
View File
@@ -25,6 +25,7 @@ struct MiniMode: View {
// Main Mini View // Main Mini View
VStack(spacing: 0) { VStack(spacing: 0) {
Group {
if appState.currentView == .empty { if appState.currentView == .empty {
TopBarMini(search: $search, showPopover: $showPopover) TopBarMini(search: $search, showPopover: $showPopover)
MiniEmptyView(showPopover: $showPopover) MiniEmptyView(showPopover: $showPopover)
@@ -32,9 +33,9 @@ struct MiniMode: View {
TopBarMini(search: $search, showPopover: $showPopover) TopBarMini(search: $search, showPopover: $showPopover)
MiniAppView(search: $search, showPopover: $showPopover) MiniAppView(search: $search, showPopover: $showPopover)
} }
} }
.transition(.move(edge: .leading)) .transition(.opacity)
}
.popover(isPresented: $showPopover, arrowEdge: .trailing) { .popover(isPresented: $showPopover, arrowEdge: .trailing) {
VStack { VStack {
if appState.currentView == .files { if appState.currentView == .files {
@@ -149,19 +150,11 @@ struct MiniAppView: View {
var body: some View { var body: some View {
var filteredUserApps: [AppInfo] { var filteredApps: [AppInfo] {
if search.isEmpty { if search.isEmpty {
return appState.sortedApps.userApps return appState.sortedApps
} else { } 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) }
} }
} }
@@ -186,7 +179,7 @@ struct MiniAppView: View {
} else { } else {
VStack(alignment: .center) { VStack(alignment: .center) {
AppsListView(search: $search, showPopover: $showPopover, filteredUserApps: filteredUserApps, filteredSystemApps: filteredSystemApps) AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps)
if appState.currentView != .empty { if appState.currentView != .empty {
SearchBarMiniBottom(search: $search).background(.ultraThinMaterial) 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 Foundation
import SwiftUI import SwiftUI
struct AppListView: View { struct RegularMode: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.glass") private var glass: Bool = false
@AppStorage("settings.general.sidebarWidth") private var sidebarWidth: Double = 280 @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 showSys: Bool = true
@State private var showUsr: Bool = true @State private var showUsr: Bool = true
@Binding var showPopover: Bool @Binding var showPopover: Bool
// @State private var isHovering = false
// @State private var isHovering2 = false
var filteredUserApps: [AppInfo] {
var filteredApps: [AppInfo] {
if search.isEmpty { if search.isEmpty {
return appState.sortedApps.userApps return appState.sortedApps
} else { } 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 { var body: some View {
@@ -66,23 +62,31 @@ struct AppListView: View {
.padding(.top, 20) .padding(.top, 20)
AppsListView(search: $search, showPopover: $showPopover, filteredUserApps: filteredUserApps, filteredSystemApps: filteredSystemApps) AppsListView(search: $search, showPopover: $showPopover, filteredApps: filteredApps)
} }
.frame(width: sidebarWidth) .frame(width: sidebarWidth)
.padding(.vertical) .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(glass ? GlassEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all) : nil)
.transition(.move(edge: .leading)) .transition(.opacity)
SlideableDivider(dimension: $sidebarWidth) SlideableDivider(dimension: $sidebarWidth)
// Details View // Details View
VStack(spacing: 0) { VStack(spacing: 0) {
Group {
if appState.currentView == .empty || appState.currentView == .apps { if appState.currentView == .empty || appState.currentView == .apps {
TopBar(showPopover: $showPopover) TopBar(showPopover: $showPopover)
AppDetailsEmptyView(showPopover: $showPopover) AppDetailsEmptyView(showPopover: $showPopover)
@@ -96,7 +100,16 @@ struct AppListView: View {
.id(appState.appInfo.id) .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) .frame(minWidth: 900, minHeight: 600)
.edgesIgnoringSafeArea(.all) .edgesIgnoringSafeArea(.all)
@@ -187,10 +200,9 @@ struct Header: View {
showPopover = false showPopover = false
let sortedApps = getSortedApps() let sortedApps = getSortedApps()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps = sortedApps
appState.sortedApps.systemApps = sortedApps.systemApps
if instantSearch { if instantSearch {
loadAllPaths(allApps: sortedApps.userApps + sortedApps.systemApps, appState: appState, locations: locations) { loadAllPaths(allApps: sortedApps, appState: appState, locations: locations) {
appState.reload.toggle() appState.reload.toggle()
} }
+1 -1
View File
@@ -53,7 +53,7 @@ struct TopBar: View {
if instantSearch { if instantSearch {
reversePathsSearch(appState: appState, locations: locations) reversePathsSearch(appState: appState, locations: locations)
} else { } 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 { } else {
appState.currentView = .zombie appState.currentView = .zombie
+2 -2
View File
@@ -39,7 +39,7 @@ struct TopBarMini: View {
if instantSearch { if instantSearch {
reversePathsSearch(appState: appState, locations: locations) reversePathsSearch(appState: appState, locations: locations)
} else { } 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 { } else {
appState.currentView = .zombie appState.currentView = .zombie
@@ -78,7 +78,7 @@ struct TopBarMini: View {
if instantSearch { if instantSearch {
reversePathsSearch(appState: appState, locations: locations) reversePathsSearch(appState: appState, locations: locations)
} else { } 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 { } else {
appState.currentView = .zombie appState.currentView = .zombie
+6 -2
View File
@@ -24,6 +24,7 @@ struct ZombieView: View {
VStack(alignment: .center) { VStack(alignment: .center) {
if appState.showProgress { if appState.showProgress {
VStack { VStack {
Group {
Spacer() Spacer()
Text("Searching the file system").font(.title3) Text("Searching the file system").font(.title3)
@@ -34,8 +35,10 @@ struct ZombieView: View {
Spacer() Spacer()
} }
.frame(maxWidth: .infinity, maxHeight: .infinity)
.transition(.opacity) .transition(.opacity)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else { } else {
// Titlebar // Titlebar
if mini { if mini {
@@ -49,7 +52,7 @@ struct ZombieView: View {
if instantSearch { if instantSearch {
reversePathsSearch(appState: appState, locations: locations) reversePathsSearch(appState: appState, locations: locations)
} else { } 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) .transition(.opacity)
.padding([.horizontal, .bottom], 20) .padding([.horizontal, .bottom], 20)
.padding(.top, !mini ? 10 : 0)
} }
+24 -6
View File
@@ -16,8 +16,14 @@ struct UpdateView: View {
VStack(spacing: 5) { VStack(spacing: 5) {
HStack { HStack {
Text("App: \(Bundle.main.version)")
.font(.title3)
.fontWeight(.semibold)
.opacity(0.5)
.padding()
Spacer() 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 🚀")") Text("\(appState.progressBar.1 != 1.0 ? "Update Available 🥳" : "Completed 🚀")")
.font(.title) .font(.title)
@@ -26,7 +32,7 @@ struct UpdateView: View {
Spacer() Spacer()
Text("v\(appState.releases.first?.tag_name ?? "")") Text("GitHub: \(appState.releases.first?.tag_name ?? "")")
.font(.title3) .font(.title3)
.fontWeight(.semibold) .fontWeight(.semibold)
.opacity(0.5) .opacity(0.5)
@@ -107,17 +113,23 @@ struct NoUpdateView: View {
VStack(spacing: 5) { VStack(spacing: 5) {
HStack { HStack {
Spacer() Text("App: \(Bundle.main.version)")
.frame(width: appState.progressBar.1 != 1.0 ? 140 : 180) .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) .font(.title)
.bold() .bold()
.padding(.vertical) .padding(.vertical)
Spacer() Spacer()
Text("v\(appState.releases.first?.tag_name ?? "")") Text("GitHub: \(appState.releases.first?.tag_name ?? "")")
.font(.title3) .font(.title3)
.fontWeight(.semibold) .fontWeight(.semibold)
.opacity(0.5) .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() Divider()
.padding([.horizontal]) .padding([.horizontal])