mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 07:19:04 +00:00
Updates
This commit is contained in:
@@ -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 */,
|
||||||
|
|||||||
@@ -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)
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -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: [:])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+131
-178
@@ -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,183 +37,154 @@ 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? {
|
||||||
if let bundle = Bundle(url: path) {
|
let filemanager = FileManager.default
|
||||||
if let bundleIdentifier = bundle.bundleIdentifier,
|
let wrapperURL = path.appendingPathComponent("Wrapper")
|
||||||
var appIconFileName = bundle.infoDictionary?["CFBundleIconFile"] as? String {
|
if filemanager.fileExists(atPath: wrapperURL.path) {
|
||||||
var appVersion = "0.0.0"
|
do {
|
||||||
var appIcon: NSImage?
|
let contents = try filemanager.contentsOfDirectory(at: wrapperURL, includingPropertiesForKeys: nil, options: [])
|
||||||
var appName: String?
|
let appFiles = contents.filter { $0.pathExtension == "app" }
|
||||||
var webApp: Bool?
|
|
||||||
|
|
||||||
|
if let firstAppFile = appFiles.first {
|
||||||
if let shortVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String {
|
let fullPath = wrapperURL.appendingPathComponent(firstAppFile.lastPathComponent)
|
||||||
appVersion = shortVersion
|
if let wrappedAppInfo = getAppInfo(atPath: fullPath, wrapped: true) {
|
||||||
} else {
|
return wrappedAppInfo
|
||||||
if let bundleVersion = bundle.infoDictionary?["CFBundleVersion"] as? String {
|
|
||||||
appVersion = bundleVersion
|
|
||||||
} else {
|
|
||||||
printOS("Failed to retrieve bundle version")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String {
|
|
||||||
appName = localizedName
|
|
||||||
} else if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String {
|
|
||||||
appName = bundleName
|
|
||||||
} else {
|
} else {
|
||||||
appName = path.deletingPathExtension().lastPathComponent
|
printOS("No .app files found in the 'Wrapper' directory: \(wrapperURL)")
|
||||||
}
|
|
||||||
// Check if the icon file name has an .icns extension, if not, add it
|
|
||||||
if !appIconFileName.hasSuffix(".icns") {
|
|
||||||
appIconFileName += ".icns"
|
|
||||||
}
|
}
|
||||||
|
} 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: String?
|
||||||
|
var appIcon: NSImage?
|
||||||
|
var appName: String?
|
||||||
|
var webApp: Bool?
|
||||||
|
var wrappedApp: Bool?
|
||||||
|
var system: Bool?
|
||||||
|
|
||||||
// Try to find the icon in the main bundle
|
if let shortVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String, !shortVersion.isEmpty {
|
||||||
if let iconPath = bundle.path(forResource: appIconFileName, ofType: nil),
|
appVersion = shortVersion
|
||||||
let icon = NSImage(contentsOfFile: iconPath) {
|
} else {
|
||||||
appIcon = icon
|
if let bundleVersion = bundle.infoDictionary?["CFBundleVersion"] as? String, !bundleVersion.isEmpty {
|
||||||
}
|
appVersion = bundleVersion
|
||||||
|
} else {
|
||||||
|
printOS("Failed to retrieve bundle version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If not found, try to find it in the resources directory
|
if let localizedName = bundle.localizedInfoDictionary?[kCFBundleNameKey as String] as? String {
|
||||||
if appIcon == nil,
|
appName = localizedName
|
||||||
let resourcesPath = bundle.resourcePath {
|
} else if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String {
|
||||||
let iconURL = URL(fileURLWithPath: resourcesPath).appendingPathComponent(appIconFileName)
|
appName = bundleName
|
||||||
if let icon = NSImage(contentsOfFile: iconURL.path) {
|
} 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find the icon in the main bundle
|
||||||
|
if let iconPath = bundle.path(forResource: appIconFileName, ofType: nil),
|
||||||
|
let icon = NSImage(contentsOfFile: iconPath) {
|
||||||
appIcon = icon
|
appIcon = icon
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 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: 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)")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} 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).")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// If not found, try to find it in the resources directory
|
||||||
|
if appIcon == nil,
|
||||||
|
let resourcesPath = bundle.resourcePath {
|
||||||
|
let iconURL = URL(fileURLWithPath: resourcesPath).appendingPathComponent(appIconFileName)
|
||||||
|
if let icon = NSImage(contentsOfFile: iconURL.path) {
|
||||||
|
appIcon = icon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the icon to a 100x100 PNG image
|
// Convert the icon to a 100x100 PNG image
|
||||||
if let pngIcon = appIcon.flatMap({ convertICNSToPNG(icon: $0, size: NSSize(width: 100, height: 100)) }) {
|
if let pngIcon = appIcon.flatMap({ convertICNSToPNG(icon: $0, size: NSSize(width: 100, height: 100)) }) {
|
||||||
appIcon = pngIcon
|
appIcon = pngIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
if appIcon == nil {
|
if appIcon == nil {
|
||||||
printOS("App Icon not found for app at path: \(path)")
|
printOS("App Icon not found for app at path: \(path)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bundle.infoDictionary?["LSTemplateApplication"] is Bool {
|
||||||
|
webApp = true
|
||||||
|
} else {
|
||||||
|
webApp = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if wrapped {
|
||||||
|
wrappedApp = true
|
||||||
|
} else {
|
||||||
|
wrappedApp = false
|
||||||
|
}
|
||||||
|
|
||||||
|
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: [:])
|
||||||
|
|
||||||
if bundle.infoDictionary?["LSTemplateApplication"] is Bool {
|
|
||||||
webApp = true
|
|
||||||
} else {
|
} else {
|
||||||
webApp = false
|
printOS("Bundle identifier not found at path: \(path)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName ?? "", appVersion: appVersion, appIcon: appIcon, webApp: webApp ?? false, wrapped: true, files: [], fileSize: [:], fileIcon: [:])
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
printOS("One or more variables missing for app at path: \(path)")
|
printOS("Bundle not found 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,19 @@
|
|||||||
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) {
|
||||||
DispatchQueue.main.async {
|
if let delay = delay {
|
||||||
updates()
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||||
|
updates()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
updates()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
Image(nsImage: appIcon)
|
ZStack {
|
||||||
.resizable()
|
// RoundedRectangle(cornerRadius: 8)
|
||||||
.aspectRatio(contentMode: .fit)
|
// .fill(Color(appIcon.averageColor!))
|
||||||
.frame(width: 30, height: 30)
|
// .frame(width: 35, height: 35)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
// .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) {
|
VStack(alignment: .leading, spacing: 5) {
|
||||||
@@ -84,17 +101,30 @@ struct AppListItems: View {
|
|||||||
.foregroundStyle(Color("mode").opacity(0.5))
|
.foregroundStyle(Color("mode").opacity(0.5))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 5)
|
||||||
}
|
.help(appInfo.appName)
|
||||||
.padding(.horizontal, 5)
|
.onHover { hovering in
|
||||||
.help(appInfo.appName)
|
withAnimation(Animation.easeIn(duration: 0.2)) {
|
||||||
.onHover { hovering in
|
self.isHovered = hovering
|
||||||
withAnimation(Animation.easeIn(duration: 0.2)) {
|
}
|
||||||
self.isHovered = hovering
|
|
||||||
}
|
}
|
||||||
}
|
.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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -25,16 +25,17 @@ struct MiniMode: View {
|
|||||||
|
|
||||||
// Main Mini View
|
// Main Mini View
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
if appState.currentView == .empty {
|
Group {
|
||||||
TopBarMini(search: $search, showPopover: $showPopover)
|
if appState.currentView == .empty {
|
||||||
MiniEmptyView(showPopover: $showPopover)
|
TopBarMini(search: $search, showPopover: $showPopover)
|
||||||
} else {
|
MiniEmptyView(showPopover: $showPopover)
|
||||||
TopBarMini(search: $search, showPopover: $showPopover)
|
} else {
|
||||||
MiniAppView(search: $search, showPopover: $showPopover)
|
TopBarMini(search: $search, showPopover: $showPopover)
|
||||||
|
MiniAppView(search: $search, showPopover: $showPopover)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
.transition(.move(edge: .leading))
|
|
||||||
.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,37 +62,54 @@ 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) {
|
||||||
if appState.currentView == .empty || appState.currentView == .apps {
|
Group {
|
||||||
TopBar(showPopover: $showPopover)
|
if appState.currentView == .empty || appState.currentView == .apps {
|
||||||
AppDetailsEmptyView(showPopover: $showPopover)
|
TopBar(showPopover: $showPopover)
|
||||||
} else if appState.currentView == .files {
|
AppDetailsEmptyView(showPopover: $showPopover)
|
||||||
TopBar(showPopover: $showPopover)
|
} else if appState.currentView == .files {
|
||||||
FilesView(showPopover: $showPopover, search: $search)
|
TopBar(showPopover: $showPopover)
|
||||||
.id(appState.appInfo.id)
|
FilesView(showPopover: $showPopover, search: $search)
|
||||||
} else if appState.currentView == .zombie {
|
.id(appState.appInfo.id)
|
||||||
TopBar(showPopover: $showPopover)
|
} else if appState.currentView == .zombie {
|
||||||
ZombieView(showPopover: $showPopover, search: $search)
|
TopBar(showPopover: $showPopover)
|
||||||
.id(appState.appInfo.id)
|
ZombieView(showPopover: $showPopover, search: $search)
|
||||||
|
.id(appState.appInfo.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.transition(.opacity)
|
||||||
}
|
}
|
||||||
.transition(.move(edge: .leading))
|
// .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()
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -24,18 +24,21 @@ struct ZombieView: View {
|
|||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
if appState.showProgress {
|
if appState.showProgress {
|
||||||
VStack {
|
VStack {
|
||||||
Spacer()
|
Group {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
Text("Searching the file system").font(.title3)
|
Text("Searching the file system").font(.title3)
|
||||||
.foregroundStyle((.gray.opacity(0.8)))
|
.foregroundStyle((.gray.opacity(0.8)))
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.progressViewStyle(.linear)
|
.progressViewStyle(.linear)
|
||||||
.frame(width: 400, height: 10)
|
.frame(width: 400, height: 10)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.transition(.opacity)
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.transition(.opacity)
|
|
||||||
} 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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user