mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 09:38:51 +00:00
Updates
This commit is contained in:
@@ -36,15 +36,6 @@ struct AppCommands: Commands {
|
||||
Text("Check for Updates")
|
||||
}
|
||||
.keyboardShortcut("u", modifiers: .command)
|
||||
|
||||
Button {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
reloadAppsList(appState: appState, fsm: fsm)
|
||||
}
|
||||
} label: {
|
||||
Text("Refresh Apps")
|
||||
}
|
||||
.keyboardShortcut("r", modifiers: .command)
|
||||
|
||||
Button {
|
||||
appState.triggerUninstallAlert()
|
||||
@@ -93,6 +84,15 @@ struct AppCommands: Commands {
|
||||
// Tools Menu
|
||||
CommandMenu(Text("Tools", comment: "Tools Menu")) {
|
||||
|
||||
Button {
|
||||
withAnimation(Animation.easeInOut(duration: animationEnabled ? 0.35 : 0)) {
|
||||
reloadAppsList(appState: appState, fsm: fsm)
|
||||
}
|
||||
} label: {
|
||||
Text("Refresh Apps")
|
||||
}
|
||||
.keyboardShortcut("r", modifiers: .command)
|
||||
|
||||
Button
|
||||
{
|
||||
if !appState.appInfo.bundleIdentifier.isEmpty {
|
||||
|
||||
@@ -9,10 +9,78 @@ import Foundation
|
||||
import SwiftUI
|
||||
import AlinFoundation
|
||||
|
||||
|
||||
// Metadata-based AppInfo Fetcher Class
|
||||
class MetadataAppInfoFetcher {
|
||||
static func getAppInfo(fromMetadata metadata: [String: Any], atPath path: URL) -> AppInfo? {
|
||||
// Extract metadata attributes for known fields
|
||||
let displayName = metadata["kMDItemDisplayName"] as? String ?? ""
|
||||
let fsName = metadata["kMDItemFSName"] as? String ?? path.lastPathComponent
|
||||
let appName = displayName.isEmpty ? fsName : displayName
|
||||
|
||||
let bundleIdentifier = metadata["kMDItemCFBundleIdentifier"] as? String ?? ""
|
||||
let version = metadata["kMDItemVersion"] as? String ?? ""
|
||||
|
||||
// Sizes
|
||||
let logicalSize = metadata["kMDItemLogicalSize"] as? Int64 ?? 0
|
||||
let physicalSize = metadata["kMDItemPhysicalSize"] as? Int64 ?? 0
|
||||
|
||||
// Check if any of the critical fields are missing or invalid
|
||||
if appName.isEmpty || bundleIdentifier.isEmpty || version.isEmpty || logicalSize == 0 || physicalSize == 0 {
|
||||
// print("Metadata is missing critical fields for \(path). Falling back to AppInfoFetcher.")
|
||||
// Fallback to the regular AppInfoFetcher for this app
|
||||
return AppInfoFetcher.getAppInfo(atPath: path)
|
||||
}
|
||||
|
||||
// Extract optional date fields
|
||||
let creationDate = metadata["kMDItemFSCreationDate"] as? Date
|
||||
let contentChangeDate = metadata["kMDItemFSContentChangeDate"] as? Date
|
||||
let lastUsedDate = metadata["kMDItemLastUsedDate"] as? Date
|
||||
|
||||
// Determine architecture type
|
||||
let arch = determineArchitecture(from: metadata)
|
||||
|
||||
// Use similar helper functions as `AppInfoFetcher` for attributes not found in metadata
|
||||
let wrapped = AppInfoFetcher.isDirectoryWrapped(path: path)
|
||||
let appIcon = AppInfoUtils.fetchAppIcon(for: path, wrapped: wrapped, md: true)
|
||||
let webApp = AppInfoUtils.isWebApp(appPath: path)
|
||||
let system = !path.path.contains(NSHomeDirectory())
|
||||
|
||||
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName,
|
||||
appVersion: version, appIcon: appIcon, webApp: webApp, wrapped: wrapped, system: system,
|
||||
arch: arch, bundleSize: logicalSize, fileSize: [:],
|
||||
fileSizeLogical: [:], fileIcon: [:], creationDate: creationDate, contentChangeDate: contentChangeDate, lastUsedDate: lastUsedDate)
|
||||
}
|
||||
|
||||
/// Determine the architecture type based on metadata
|
||||
private static func determineArchitecture(from metadata: [String: Any]) -> Arch {
|
||||
guard let architectures = metadata["kMDItemExecutableArchitectures"] as? [String] else {
|
||||
return .empty
|
||||
}
|
||||
|
||||
// Check for ARM and Intel presence
|
||||
let containsArm = architectures.contains("arm64")
|
||||
let containsIntel = architectures.contains("x86_64")
|
||||
|
||||
// Determine the Arch type based on available architectures
|
||||
if containsArm && containsIntel {
|
||||
return .universal
|
||||
} else if containsArm {
|
||||
return .arm
|
||||
} else if containsIntel {
|
||||
return .intel
|
||||
} else {
|
||||
return .empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class AppInfoFetcher {
|
||||
static let fileManager = FileManager.default
|
||||
|
||||
static func getAppInfo(atPath path: URL, wrapped: Bool = false) -> AppInfo? {
|
||||
public static func getAppInfo(atPath path: URL, wrapped: Bool = false) -> AppInfo? {
|
||||
if isDirectoryWrapped(path: path) {
|
||||
return handleWrappedDirectory(atPath: path)
|
||||
} else {
|
||||
@@ -20,7 +88,7 @@ class AppInfoFetcher {
|
||||
}
|
||||
}
|
||||
|
||||
private static func isDirectoryWrapped(path: URL) -> Bool {
|
||||
public static func isDirectoryWrapped(path: URL) -> Bool {
|
||||
let wrapperURL = path.appendingPathComponent("Wrapper")
|
||||
return fileManager.fileExists(atPath: wrapperURL.path)
|
||||
}
|
||||
@@ -53,17 +121,36 @@ class AppInfoFetcher {
|
||||
? bundle.infoDictionary?["CFBundleVersion"] as? String ?? ""
|
||||
: bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
|
||||
|
||||
let appIcon = fetchAppIcon(for: path, wrapped: wrapped)
|
||||
let webApp = (bundle.infoDictionary?["LSTemplateApplication"] as? Bool ?? false || bundle.infoDictionary?["CFBundleExecutable"] as? String == "app_mode_loader")
|
||||
let appIcon = AppInfoUtils.fetchAppIcon(for: path, wrapped: wrapped)
|
||||
let webApp = AppInfoUtils.isWebApp(bundle: bundle)
|
||||
|
||||
|
||||
let system = !path.path.contains(NSHomeDirectory())
|
||||
|
||||
return AppInfo(id: UUID(), path: path, bundleIdentifier: bundleIdentifier, appName: appName, appVersion: appVersion, appIcon: appIcon,
|
||||
webApp: webApp, wrapped: wrapped, system: system, arch: .empty, bundleSize: 0, files: [], fileSize: [:], fileSizeLogical: [:], fileIcon: [:])
|
||||
webApp: webApp, wrapped: wrapped, system: system, arch: .empty, bundleSize: 0, fileSize: [:], fileSizeLogical: [:], fileIcon: [:], creationDate: nil, contentChangeDate: nil, lastUsedDate: nil)
|
||||
}
|
||||
|
||||
private static func fetchAppIcon(for path: URL, wrapped: Bool) -> NSImage? {
|
||||
let iconPath = wrapped ? path.deletingLastPathComponent().deletingLastPathComponent() : path
|
||||
}
|
||||
|
||||
|
||||
class AppInfoUtils {
|
||||
/// Determines if the app is a web application by directly reading its `Info.plist` using the app path.
|
||||
static func isWebApp(appPath: URL) -> Bool {
|
||||
guard let bundle = Bundle(url: appPath) else { return false }
|
||||
return isWebApp(bundle: bundle)
|
||||
}
|
||||
|
||||
/// Determines if the app is a web application based on its bundle.
|
||||
static func isWebApp(bundle: Bundle?) -> Bool {
|
||||
guard let infoDict = bundle?.infoDictionary else { return false }
|
||||
return (infoDict["LSTemplateApplication"] as? Bool ?? false) ||
|
||||
(infoDict["CFBundleExecutable"] as? String == "app_mode_loader")
|
||||
}
|
||||
|
||||
/// Fetch app icon.
|
||||
static func fetchAppIcon(for path: URL, wrapped: Bool, md: Bool = false) -> NSImage? {
|
||||
let iconPath = wrapped ? (md ? path : path.deletingLastPathComponent().deletingLastPathComponent()) : path
|
||||
if let appIcon = getIconForFileOrFolderNS(atPath: iconPath) {
|
||||
return convertICNSToPNG(icon: appIcon, size: NSSize(width: 100, height: 100))
|
||||
} else {
|
||||
@@ -71,5 +158,101 @@ class AppInfoFetcher {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Executes `mdls` with `-plist -` and `-nullMarker ""` options and returns metadata in a structured dictionary.
|
||||
func getMDLSMetadataAsPlist(for paths: [String]) -> [String: [String: Any]]? {
|
||||
let task = Process()
|
||||
task.executableURL = URL(fileURLWithPath: "/usr/bin/mdls")
|
||||
|
||||
// Define the required metadata attributes to include in the output
|
||||
let attributes = [
|
||||
"kMDItemFSCreationDate",
|
||||
"kMDItemFSContentChangeDate",
|
||||
"kMDItemLastUsedDate",
|
||||
"kMDItemDisplayName",
|
||||
"kMDItemAppStoreCategory",
|
||||
"kMDItemCFBundleIdentifier",
|
||||
"kMDItemExecutableArchitectures",
|
||||
"kMDItemFSName",
|
||||
"kMDItemVersion",
|
||||
"kMDItemLogicalSize",
|
||||
"kMDItemPhysicalSize"
|
||||
]
|
||||
|
||||
// Construct the `mdls` arguments: paths + -name for each attribute + -plist - + -nullMarker ""
|
||||
var arguments = paths
|
||||
for attribute in attributes {
|
||||
arguments.append("-name")
|
||||
arguments.append(attribute)
|
||||
}
|
||||
arguments.append("-plist") // Use plist format
|
||||
arguments.append("-") // Output to stdout
|
||||
arguments.append("-nullMarker") // Replace null attributes
|
||||
arguments.append("") // Substitute null values with empty string
|
||||
|
||||
task.arguments = arguments
|
||||
|
||||
// Use Pipe to capture the output
|
||||
let pipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
task.standardOutput = pipe
|
||||
task.standardError = errorPipe // Capture stderr in case there are errors
|
||||
|
||||
do {
|
||||
// Run the task
|
||||
try task.run()
|
||||
|
||||
// Read the data from the pipe
|
||||
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||
|
||||
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
|
||||
if let errorOutput = String(data: errorData, encoding: .utf8), !errorOutput.isEmpty {
|
||||
print("Error Output from mdls:\n\(errorOutput)\n")
|
||||
}
|
||||
|
||||
// Check if there's any output captured
|
||||
if data.isEmpty {
|
||||
print("No output captured from mdls.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Attempt to parse the plist output into a Swift array of dictionaries
|
||||
guard let plistArray = try PropertyListSerialization.propertyList(from: data, format: nil) as? [[String: Any]] else {
|
||||
print("Failed to parse plist output into expected format.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// print("Parsed plist array count: \(plistArray.count)")
|
||||
|
||||
// Ensure the number of plist items matches the number of paths
|
||||
if plistArray.count != paths.count {
|
||||
print("Warning: Number of plist items (\(plistArray.count)) does not match the number of paths (\(paths.count)).")
|
||||
}
|
||||
|
||||
var metadataDictionary = [String: [String: Any]]()
|
||||
|
||||
// Map each metadata dictionary to its corresponding path using indices
|
||||
for (index, appMetadata) in plistArray.enumerated() {
|
||||
// Match metadata to path using the index
|
||||
if index < paths.count {
|
||||
let path = paths[index]
|
||||
metadataDictionary[path] = appMetadata
|
||||
// print("Mapped metadata to path: \(path)")
|
||||
} else {
|
||||
print("Warning: More metadata entries than paths.")
|
||||
}
|
||||
}
|
||||
return metadataDictionary
|
||||
|
||||
} catch {
|
||||
print("Error running mdls: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class AppState: ObservableObject {
|
||||
@Published var sortedApps: [AppInfo] = []
|
||||
@Published var selectedItems = Set<URL>()
|
||||
@Published var currentView = CurrentDetailsView.empty
|
||||
@Published var currentPage = CurrentPage.installed
|
||||
@Published var showAlert: Bool = false
|
||||
@Published var sidebar: Bool = true
|
||||
@Published var reload: Bool = false
|
||||
@@ -67,10 +68,13 @@ class AppState: ObservableObject {
|
||||
system: false,
|
||||
arch: .empty,
|
||||
bundleSize: 0,
|
||||
files: [],
|
||||
// files: [],
|
||||
fileSize: [:],
|
||||
fileSizeLogical: [:],
|
||||
fileIcon: [:]
|
||||
fileIcon: [:],
|
||||
creationDate: nil,
|
||||
contentChangeDate: nil,
|
||||
lastUsedDate: nil
|
||||
)
|
||||
|
||||
self.zombieFile = ZombieFile(
|
||||
@@ -137,12 +141,15 @@ struct AppInfo: Identifiable, Equatable, Hashable {
|
||||
let wrapped: Bool
|
||||
let system: Bool
|
||||
var arch: Arch
|
||||
var bundleSize: Int64
|
||||
var files: [URL]
|
||||
var bundleSize: Int64 // Only used in the app list view
|
||||
// var files: [URL]
|
||||
var fileSize: [URL:Int64]
|
||||
var fileSizeLogical: [URL:Int64]
|
||||
var fileIcon: [URL:NSImage?]
|
||||
var totalSize: Int64
|
||||
let creationDate: Date?
|
||||
let contentChangeDate: Date?
|
||||
let lastUsedDate: Date?
|
||||
var totalSize: Int64
|
||||
{
|
||||
return fileSize.values.reduce(0, +)
|
||||
}
|
||||
@@ -150,9 +157,10 @@ struct AppInfo: Identifiable, Equatable, Hashable {
|
||||
{
|
||||
return fileSizeLogical.values.reduce(0, +)
|
||||
}
|
||||
|
||||
|
||||
|
||||
static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil, webApp: false, wrapped: false, system: false, arch: .empty, bundleSize: 0, files: [], fileSize: [:], fileSizeLogical: [:], fileIcon: [:])
|
||||
static let empty = AppInfo(id: UUID(), path: URL(fileURLWithPath: ""), bundleIdentifier: "", appName: "", appVersion: "", appIcon: nil, webApp: false, wrapped: false, system: false, arch: .empty, bundleSize: 0, fileSize: [:], fileSizeLogical: [:], fileIcon: [:], creationDate: nil, contentChangeDate: nil, lastUsedDate: nil)
|
||||
|
||||
}
|
||||
|
||||
@@ -185,6 +193,19 @@ enum Arch {
|
||||
case empty
|
||||
}
|
||||
|
||||
enum CurrentPage:Int
|
||||
{
|
||||
case installed
|
||||
case uninstalled
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .installed: return "Installed"
|
||||
case .uninstalled: return "Uninstalled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum CurrentTabView:Int
|
||||
{
|
||||
|
||||
@@ -37,9 +37,40 @@ func getSortedApps(paths: [String]) -> [AppInfo] {
|
||||
// Collect system applications
|
||||
paths.forEach { collectAppPaths(at: $0) }
|
||||
|
||||
// let startTime = Date()
|
||||
|
||||
// Convert collected paths to string format for metadata query
|
||||
let combinedPaths = apps.map { $0.path }
|
||||
|
||||
// Get metadata for all collected app paths
|
||||
var metadataDictionary: [String: [String: Any]] = [:]
|
||||
if let metadata = getMDLSMetadataAsPlist(for: combinedPaths) {
|
||||
metadataDictionary = metadata
|
||||
}
|
||||
|
||||
// Process each app path and construct AppInfo using metadata first, then fallback if necessary
|
||||
let appInfos: [AppInfo] = apps.compactMap { appURL in
|
||||
let appPath = appURL.path
|
||||
|
||||
if let appMetadata = metadataDictionary[appPath] {
|
||||
// Use `MetadataAppInfoFetcher` first
|
||||
return MetadataAppInfoFetcher.getAppInfo(fromMetadata: appMetadata, atPath: appURL)
|
||||
} else {
|
||||
// Fallback to `AppInfoFetcher` if no metadata found
|
||||
return AppInfoFetcher.getAppInfo(atPath: appURL)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort apps by display name
|
||||
let sortedApps = appInfos.sorted { $0.appName.lowercased() < $1.appName.lowercased() }
|
||||
|
||||
// Get app info and sort
|
||||
let sortedApps = apps
|
||||
.compactMap { AppInfoFetcher.getAppInfo(atPath: $0) }
|
||||
// let sortedApps = apps
|
||||
// .compactMap { AppInfoFetcher.getAppInfo(atPath: $0) }
|
||||
|
||||
|
||||
// let elapsedTime = Date().timeIntervalSince(startTime)
|
||||
// print("Time taken for mdls metadata extraction: \(elapsedTime) seconds")
|
||||
|
||||
return sortedApps
|
||||
}
|
||||
@@ -169,7 +200,7 @@ func moveFilesToTrash(appState: AppState, at fileURLs: [URL], completion: @escap
|
||||
// Stop Sentinel FileWatcher momentarily to ignore .app bundle being sent to Trash
|
||||
sendStopNotificationFW()
|
||||
|
||||
updateOnBackground {
|
||||
updateOnMain {
|
||||
let posixFiles = fileURLs.map { item in
|
||||
return "POSIX file \"\(item.path)\"" + (item == fileURLs.last ? "" : ", ")}.joined()
|
||||
let scriptSource = """
|
||||
@@ -182,19 +213,15 @@ func moveFilesToTrash(appState: AppState, at fileURLs: [URL], completion: @escap
|
||||
|
||||
// Handle any AppleScript errors
|
||||
if let error = error {
|
||||
DispatchQueue.main.async {
|
||||
printOS("Trash Error: \(error)")
|
||||
completion(false) // Indicate failure
|
||||
}
|
||||
printOS("Trash Error: \(error)")
|
||||
completion(false) // Indicate failure
|
||||
return
|
||||
}
|
||||
|
||||
// Check if output is null, indicating the user canceled the operation
|
||||
if output.descriptorType == typeNull {
|
||||
DispatchQueue.main.async {
|
||||
printOS("Trash Error: operation canceled by the user")
|
||||
completion(false) // Indicate failure due to cancellation
|
||||
}
|
||||
printOS("Trash Error: operation canceled by the user")
|
||||
completion(false) // Indicate failure due to cancellation
|
||||
return
|
||||
}
|
||||
|
||||
@@ -203,9 +230,7 @@ func moveFilesToTrash(appState: AppState, at fileURLs: [URL], completion: @escap
|
||||
printOS("Trash: \(outputString)")
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
completion(true) // Indicate success
|
||||
}
|
||||
completion(true) // Indicate success
|
||||
}
|
||||
|
||||
}
|
||||
@@ -265,7 +290,7 @@ func undoTrash(appState: AppState, completion: @escaping () -> Void = {}) {
|
||||
"""
|
||||
var error: NSDictionary?
|
||||
|
||||
updateOnBackground {
|
||||
updateOnMain {
|
||||
if let scriptObject = NSAppleScript(source: scriptSource) {
|
||||
let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error)
|
||||
if let error = error {
|
||||
@@ -274,9 +299,7 @@ func undoTrash(appState: AppState, completion: @escaping () -> Void = {}) {
|
||||
printOS(outputString)
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -303,23 +303,26 @@ func manageSymlink(install: Bool) {
|
||||
"""
|
||||
}
|
||||
|
||||
// Execute the AppleScript
|
||||
var error: NSDictionary?
|
||||
if let scriptObject = NSAppleScript(source: script) {
|
||||
scriptObject.executeAndReturnError(&error)
|
||||
updateOnMain {
|
||||
// Execute the AppleScript
|
||||
var error: NSDictionary?
|
||||
if let scriptObject = NSAppleScript(source: script) {
|
||||
scriptObject.executeAndReturnError(&error)
|
||||
|
||||
if let error = error {
|
||||
printOS("AppleScript Error: \(error)")
|
||||
} else {
|
||||
if install {
|
||||
printOS("Symlink created successfully at \(symlinkPath).")
|
||||
if let error = error {
|
||||
printOS("AppleScript Error: \(error)")
|
||||
} else {
|
||||
printOS("Symlink removed successfully from \(symlinkPath).")
|
||||
if install {
|
||||
printOS("Symlink created successfully at \(symlinkPath).")
|
||||
} else {
|
||||
printOS("Symlink removed successfully from \(symlinkPath).")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printOS("Error: Unable to create the AppleScript object.")
|
||||
}
|
||||
} else {
|
||||
printOS("Error: Unable to create the AppleScript object.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -361,14 +364,17 @@ func caskCleanup(app: String) {
|
||||
end tell
|
||||
"""
|
||||
|
||||
var error: NSDictionary?
|
||||
if let appleScript = NSAppleScript(source: script) {
|
||||
appleScript.executeAndReturnError(&error)
|
||||
updateOnMain {
|
||||
var error: NSDictionary?
|
||||
if let appleScript = NSAppleScript(source: script) {
|
||||
appleScript.executeAndReturnError(&error)
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
printOS("AppleScript Error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
printOS("AppleScript Error: \(error)")
|
||||
}
|
||||
} else {
|
||||
printOS("Brew cleanup: No cask found for \(app).")
|
||||
}
|
||||
@@ -453,15 +459,17 @@ func caskCleanup2(app: String) {
|
||||
fi'" in front window
|
||||
end tell
|
||||
"""
|
||||
updateOnMain {
|
||||
var error: NSDictionary?
|
||||
if let appleScript = NSAppleScript(source: script) {
|
||||
appleScript.executeAndReturnError(&error)
|
||||
}
|
||||
|
||||
var error: NSDictionary?
|
||||
if let appleScript = NSAppleScript(source: script) {
|
||||
appleScript.executeAndReturnError(&error)
|
||||
if let error = error {
|
||||
print("AppleScript Error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
print("AppleScript Error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,6 +804,15 @@ func isNested(path: URL) -> Bool {
|
||||
}
|
||||
|
||||
|
||||
// Date formatter for metadata
|
||||
func formattedMDDate(from date: Date) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm zzz"
|
||||
formatter.timeZone = .current // Use the current timezone
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Extend String to remove periods, spaces and lowercase the string
|
||||
extension String {
|
||||
|
||||
@@ -35,12 +35,12 @@ class WindowSettings: ObservableObject {
|
||||
func newWindow<V: View>(mini: Bool, withView view: @escaping () -> V) {
|
||||
let frame = self.resetWindowSettings(mini: mini)
|
||||
|
||||
if menubarEnabled {
|
||||
windowRef = NSWindow(
|
||||
contentRect: .zero,
|
||||
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
|
||||
backing: .buffered, defer: false)
|
||||
}
|
||||
// if menubarEnabled {
|
||||
// windowRef = NSWindow(
|
||||
// contentRect: .zero,
|
||||
// styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
|
||||
// backing: .buffered, defer: false)
|
||||
// }
|
||||
// Update the existing windowRef with all desired settings
|
||||
windowRef?.contentView = NSHostingView(rootView: view())
|
||||
windowRef?.setFrame(frame, display: true, animate: true)
|
||||
|
||||
Reference in New Issue
Block a user