Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CleanMac.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@
CODE_SIGN_ENTITLEMENTS = CleanMac/CleanMac.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 4;
ENABLE_APP_SANDBOX = NO;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
Expand All @@ -275,7 +275,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 0.2.1;
MARKETING_VERSION = 0.3.0;
PRODUCT_BUNDLE_IDENTIFIER = com.codex.cleanmac;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand All @@ -296,7 +296,7 @@
CODE_SIGN_ENTITLEMENTS = CleanMac/CleanMac.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 4;
ENABLE_APP_SANDBOX = NO;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
Expand All @@ -310,7 +310,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 0.2.1;
MARKETING_VERSION = 0.3.0;
PRODUCT_BUNDLE_IDENTIFIER = com.codex.cleanmac;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand Down
29 changes: 24 additions & 5 deletions CleanMac/CleanMacApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ struct CleanMacApp: App {
@Environment(\.openWindow) private var openWindow
@AppStorage(CleanMacAppearance.storageKey) private var appearanceMode = CleanMacAppearance.defaultCode
@AppStorage(CleanMacLanguage.storageKey) private var languageCode = CleanMacLanguage.defaultCode
@AppStorage(CleanMacPreferenceKeys.onboardingCompleted) private var onboardingCompleted = false

private var language: CleanMacLanguage {
CleanMacLanguage(rawValue: languageCode) ?? .current
Expand All @@ -18,9 +19,18 @@ struct CleanMacApp: App {

var body: some Scene {
WindowGroup("CleanMac", id: "main") {
MainWindowView()
.environment(\.locale, language.locale)
.preferredColorScheme(appearance.colorScheme)
Group {
if onboardingCompleted {
MainWindowView()
.preferredColorScheme(appearance.colorScheme)
} else {
OnboardingView {
onboardingCompleted = true
}
.preferredColorScheme(nil)
}
}
.environment(\.locale, language.locale)
}
.defaultSize(width: 980, height: 720)
.windowResizability(.contentMinSize)
Expand All @@ -44,7 +54,7 @@ struct CleanMacApp: App {
MenuBarExtra("CleanMac", image: "MenuBarIcon") {
StatusMenuView()
.environment(\.locale, language.locale)
.preferredColorScheme(appearance.colorScheme)
.environment(\.colorScheme, appearance.colorScheme)
}
.menuBarExtraStyle(.window)
}
Expand All @@ -54,9 +64,9 @@ final class CleanMacAppDelegate: NSObject, NSApplicationDelegate {
private let autoScanScheduler = CleanMacAutoScanScheduler()

func applicationDidFinishLaunching(_ notification: Notification) {
MainWindowController.prepareForInitialPresentation(isBackgroundLaunch: !NSApp.isActive)
NSApp.setActivationPolicy(.regular)
configureDockIcon()
NSApp.activate(ignoringOtherApps: true)
CleanMacNotificationService.configure()
requestNotificationAuthorizationIfUseful()
autoScanScheduler.start()
Expand All @@ -66,6 +76,15 @@ final class CleanMacAppDelegate: NSObject, NSApplicationDelegate {
false
}

func applicationWillBecomeActive(_ notification: Notification) {
MainWindowController.handleReopen()
}

func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
MainWindowController.handleReopen()
return true
}

func applicationWillTerminate(_ notification: Notification) {
autoScanScheduler.stop()
}
Expand Down
93 changes: 90 additions & 3 deletions CleanMac/Models/CleanMacModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ enum CleanMacSection: String, CaseIterable, Identifiable {
case dashboard
case scan
case results
case diskAnalysis
case duplicates
case applications
case permissions
case settings

var id: String { rawValue }
Expand All @@ -16,8 +17,9 @@ enum CleanMacSection: String, CaseIterable, Identifiable {
case .dashboard: L.t("section.dashboard")
case .scan: L.t("section.scan")
case .results: L.t("section.results")
case .diskAnalysis: L.t("section.diskAnalysis")
case .duplicates: L.t("section.duplicates")
case .applications: L.t("section.applications")
case .permissions: L.t("section.permissions")
case .settings: L.t("section.settings")
}
}
Expand All @@ -27,8 +29,9 @@ enum CleanMacSection: String, CaseIterable, Identifiable {
case .dashboard: "gauge.with.dots.needle.67percent"
case .scan: "magnifyingglass"
case .results: "checklist"
case .diskAnalysis: "chart.pie.fill"
case .duplicates: "square.on.square"
case .applications: "app.badge.checkmark"
case .permissions: "lock.shield"
case .settings: "gearshape"
}
}
Expand Down Expand Up @@ -112,6 +115,9 @@ extension CleanupScanReason {
case .browserCache: L.t("results.reason.browserCache.title")
case .nodePackageCache: L.t("results.reason.nodePackageCache.title")
case .swiftPackageCache: L.t("results.reason.swiftPackageCache.title")
case .developerPackageCache: L.t("results.reason.developerPackageCache.title")
case .developerIDECache: L.t("results.reason.developerIDECache.title")
case .developerAITemporaryFile: L.t("results.reason.developerAITemporaryFile.title")
case .staleLog: L.t("results.reason.staleLog.title")
case .rotatedLog: L.t("results.reason.rotatedLog.title")
case .staleTemporary: L.t("results.reason.staleTemporary.title")
Expand All @@ -120,6 +126,10 @@ extension CleanupScanReason {
case .oldDownload: L.t("results.reason.oldDownload.title")
case .installerArchive: L.t("results.reason.installerArchive.title")
case .xcodeBuildData: L.t("results.reason.xcodeBuildData.title")
case .xcodeDeviceSupport: L.t("results.reason.xcodeDeviceSupport.title")
case .xcodePreviewData: L.t("results.reason.xcodePreviewData.title")
case .staleSimulatorData: L.t("results.reason.staleSimulatorData.title")
case .xcodeArchive: L.t("results.reason.xcodeArchive.title")
}
}

Expand All @@ -129,6 +139,9 @@ extension CleanupScanReason {
case .browserCache: L.t("results.reason.browserCache.detail")
case .nodePackageCache: L.t("results.reason.nodePackageCache.detail")
case .swiftPackageCache: L.t("results.reason.swiftPackageCache.detail")
case .developerPackageCache: L.t("results.reason.developerPackageCache.detail")
case .developerIDECache: L.t("results.reason.developerIDECache.detail")
case .developerAITemporaryFile: L.t("results.reason.developerAITemporaryFile.detail")
case .staleLog: L.t("results.reason.staleLog.detail")
case .rotatedLog: L.t("results.reason.rotatedLog.detail")
case .staleTemporary: L.t("results.reason.staleTemporary.detail")
Expand All @@ -137,6 +150,10 @@ extension CleanupScanReason {
case .oldDownload: L.t("results.reason.oldDownload.detail")
case .installerArchive: L.t("results.reason.installerArchive.detail")
case .xcodeBuildData: L.t("results.reason.xcodeBuildData.detail")
case .xcodeDeviceSupport: L.t("results.reason.xcodeDeviceSupport.detail")
case .xcodePreviewData: L.t("results.reason.xcodePreviewData.detail")
case .staleSimulatorData: L.t("results.reason.staleSimulatorData.detail")
case .xcodeArchive: L.t("results.reason.xcodeArchive.detail")
}
}

Expand All @@ -146,6 +163,9 @@ extension CleanupScanReason {
case .browserCache: "safari"
case .nodePackageCache: "curlybraces.square"
case .swiftPackageCache: "swift"
case .developerPackageCache: "shippingbox.and.arrow.backward"
case .developerIDECache: "curlybraces.square"
case .developerAITemporaryFile: "sparkles.rectangle.stack"
case .staleLog: "doc.text.magnifyingglass"
case .rotatedLog: "arrow.triangle.2.circlepath"
case .staleTemporary: "clock.arrow.circlepath"
Expand All @@ -154,6 +174,10 @@ extension CleanupScanReason {
case .oldDownload: "calendar.badge.clock"
case .installerArchive: "opticaldiscdrive"
case .xcodeBuildData: "hammer"
case .xcodeDeviceSupport: "iphone.gen3"
case .xcodePreviewData: "rectangle.on.rectangle.angled"
case .staleSimulatorData: "iphone.gen3.slash"
case .xcodeArchive: "archivebox"
}
}
}
Expand Down Expand Up @@ -311,6 +335,33 @@ enum CleanMacCatalog {
risk: .safe,
isDefaultSelected: true
),
CleanupArea(
category: .developerPackageCaches,
title: L.t("area.developerPackages.title"),
detail: L.t("area.developerPackages.detail"),
pathHint: "Homebrew, pip, ~/.cargo/registry, ~/.gradle/caches",
systemImage: "shippingbox.and.arrow.backward",
risk: .safe,
isDefaultSelected: true
),
CleanupArea(
category: .developerIDECaches,
title: L.t("area.developerIDEs.title"),
detail: L.t("area.developerIDEs.detail"),
pathHint: "Cursor, Visual Studio Code — Cache only",
systemImage: "curlybraces.square",
risk: .safe,
isDefaultSelected: true
),
CleanupArea(
category: .developerAITemporaryFiles,
title: L.t("area.developerAI.title"),
detail: L.t("area.developerAI.detail"),
pathHint: "~/.codex/{.tmp,tmp,cache}, ~/.claude/{cache,paste-cache}",
systemImage: "sparkles.rectangle.stack",
risk: .safe,
isDefaultSelected: true
),
CleanupArea(
category: .logs,
title: L.t("area.logs.title"),
Expand Down Expand Up @@ -364,6 +415,42 @@ enum CleanMacCatalog {
systemImage: "hammer",
risk: .review,
isDefaultSelected: true
),
CleanupArea(
category: .xcodeDeviceSupport,
title: L.t("area.xcodeDeviceSupport.title"),
detail: L.t("area.xcodeDeviceSupport.detail"),
pathHint: "~/Library/Developer/Xcode/iOS DeviceSupport",
systemImage: "iphone.gen3",
risk: .review,
isDefaultSelected: false
),
CleanupArea(
category: .xcodePreviews,
title: L.t("area.xcodePreviews.title"),
detail: L.t("area.xcodePreviews.detail"),
pathHint: "~/Library/Developer/Xcode/UserData/Previews",
systemImage: "rectangle.on.rectangle.angled",
risk: .safe,
isDefaultSelected: true
),
CleanupArea(
category: .xcodeSimulatorData,
title: L.t("area.xcodeSimulator.title"),
detail: L.t("area.xcodeSimulator.detail"),
pathHint: "~/Library/Developer/CoreSimulator/{Devices,Profiles/Runtimes}",
systemImage: "iphone.gen3.slash",
risk: .review,
isDefaultSelected: false
),
CleanupArea(
category: .xcodeArchives,
title: L.t("area.xcodeArchives.title"),
detail: L.t("area.xcodeArchives.detail"),
pathHint: "~/Library/Developer/Xcode/Archives/*.xcarchive",
systemImage: "archivebox",
risk: .review,
isDefaultSelected: false
)
]
}
Expand Down
1 change: 1 addition & 0 deletions CleanMac/Support/CleanMacPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import CleanMacCore
import Foundation

enum CleanMacPreferenceKeys {
static let onboardingCompleted = "CleanMac.onboardingCompleted"
static let selectedAreaIDs = "CleanMac.selectedAreaIDs"
static let lastScanItemCount = "CleanMac.lastScanItemCount"
static let lastScanBytes = "CleanMac.lastScanBytes"
Expand Down
27 changes: 27 additions & 0 deletions CleanMac/Support/DiskAnalysisWorkspaceService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import AppKit

@MainActor
enum DiskAnalysisWorkspaceService {
static func chooseFolder() -> URL? {
let panel = NSOpenPanel()
panel.canChooseFiles = false
panel.canChooseDirectories = true
panel.allowsMultipleSelection = false
panel.canCreateDirectories = false
panel.prompt = L.t("disk.source.choose.confirm")
panel.message = L.t("disk.source.choose.message")
return panel.runModal() == .OK ? panel.url?.resolvingSymlinksInPath().standardizedFileURL : nil
}

static func reveal(_ url: URL) async {
let revealedWithAutomation = await CleanMacAutomationService.revealInFinder(url)
if !revealedWithAutomation {
NSWorkspace.shared.activateFileViewerSelecting([url])
}
}

@discardableResult
static func open(_ url: URL) -> Bool {
NSWorkspace.shared.open(url)
}
}
26 changes: 26 additions & 0 deletions CleanMac/Support/DuplicateWorkspaceService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import AppKit
import CleanMacCore

@MainActor
enum DuplicateWorkspaceService {
static func chooseFolder() -> URL? {
let panel = NSOpenPanel()
panel.canChooseFiles = false
panel.canChooseDirectories = true
panel.allowsMultipleSelection = false
panel.canCreateDirectories = false
panel.prompt = L.t("duplicates.source.choose.confirm")
panel.message = L.t("duplicates.source.choose.message")
return panel.runModal() == .OK
? panel.url?.resolvingSymlinksInPath().standardizedFileURL
: nil
}

static func reveal(_ file: DuplicateFile) async {
let url = URL(fileURLWithPath: file.path)
let revealedWithAutomation = await CleanMacAutomationService.revealInFinder(url)
if !revealedWithAutomation {
NSWorkspace.shared.activateFileViewerSelecting([url])
}
}
}
Loading