Skip to content

Commit f4a1966

Browse files
MehmedHunjraclaude
andcommitted
fix: bug fixes and menu bar popup positioning
- ApplicationsManagerView, DevCleanerView, PerformanceManagerView, PrivacyView: remove scanAll() call from OK/Done dismiss buttons; add Full Disk Access alert when 0 bytes cleaned; track actual bytes deleted instead of planned bytes (PrivacyView) - MenuBarView: add MenuBarPopupPositioner — forces menu bar popup to top-right corner on every open using NSWindow.didBecomeKeyNotification so it persists across repeated clicks - MacSweepApp: restore main window to center positioning Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e451d53 commit f4a1966

6 files changed

Lines changed: 125 additions & 15 deletions

File tree

MacSweep/ApplicationsManagerView.swift

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ struct ApplicationsManagerView: View {
3636
@State private var showClearAllConfirm = false
3737
@State private var showResult = false
3838
@State private var resultMessage = ""
39+
@State private var showFDAAlert = false
3940
@State private var searchText = ""
4041
@State private var activeFilter: AppFilter = .all
4142
@State private var activeSort: AppSort = .name
@@ -137,10 +138,18 @@ struct ApplicationsManagerView: View {
137138
}
138139
}
139140
.alert("Action Complete", isPresented: $showResult) {
140-
Button("OK") { engine.scanAll() }
141+
Button("OK") { }
141142
} message: {
142143
Text(resultMessage)
143144
}
145+
.alert("Full Disk Access Required", isPresented: $showFDAAlert) {
146+
Button("Open System Settings") {
147+
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!)
148+
}
149+
Button("Cancel", role: .cancel) { }
150+
} message: {
151+
Text("MacSweep needs Full Disk Access to clean app files.\n\nGo to System Settings → Privacy & Security → Full Disk Access and enable MacSweep.")
152+
}
144153
}
145154

146155
// MARK: - Landing
@@ -877,8 +886,12 @@ struct ApplicationsManagerView: View {
877886
.confirmationDialog("Clean All App Leftovers?", isPresented: $showBatchCleanConfirm, titleVisibility: .visible) {
878887
Button("Clean Selected Leftovers from \(appsWithLeftovers) Apps", role: .destructive) {
879888
let cleaned = engine.batchCleanAllLeftovers()
880-
resultMessage = "Batch cleaned \(ByteCountFormatter.string(fromByteCount: cleaned, countStyle: .file)) from \(appsWithLeftovers) apps."
881-
showResult = true
889+
if cleaned == 0 {
890+
showFDAAlert = true
891+
} else {
892+
resultMessage = "Cleaned \(ByteCountFormatter.string(fromByteCount: cleaned, countStyle: .file)) from \(appsWithLeftovers) apps."
893+
showResult = true
894+
}
882895
}
883896
Button("Cancel", role: .cancel) {}
884897
} message: {
@@ -922,8 +935,12 @@ struct ApplicationsManagerView: View {
922935
.confirmationDialog("Reset \(app.name)?", isPresented: $showResetConfirm, titleVisibility: .visible) {
923936
Button("Reset (Delete Leftovers)", role: .destructive) {
924937
let cleaned = engine.resetApp(appId: app.id)
925-
resultMessage = "Cleaned \(ByteCountFormatter.string(fromByteCount: cleaned, countStyle: .file)) of data from \(app.name)."
926-
showResult = true
938+
if cleaned == 0 {
939+
showFDAAlert = true
940+
} else {
941+
resultMessage = "Cleaned \(ByteCountFormatter.string(fromByteCount: cleaned, countStyle: .file)) from \(app.name)."
942+
showResult = true
943+
}
927944
}
928945
Button("Cancel", role: .cancel) {}
929946
}
@@ -944,8 +961,12 @@ struct ApplicationsManagerView: View {
944961
.confirmationDialog("Clear All App Data for \(app.name)?", isPresented: $showClearAllConfirm, titleVisibility: .visible) {
945962
Button("Deep Clean (Delete Leftovers + Scratch)", role: .destructive) {
946963
let cleaned = engine.clearAllAppData(appId: app.id)
947-
resultMessage = "Deep cleaned \(ByteCountFormatter.string(fromByteCount: cleaned, countStyle: .file)) from \(app.name)."
948-
showResult = true
964+
if cleaned == 0 {
965+
showFDAAlert = true
966+
} else {
967+
resultMessage = "Deep cleaned \(ByteCountFormatter.string(fromByteCount: cleaned, countStyle: .file)) from \(app.name)."
968+
showResult = true
969+
}
949970
}
950971
Button("Cancel", role: .cancel) {}
951972
} message: { Text("This will move ALL detected caches, logs, preferences, and temporary files for this app to Trash when possible.") }

MacSweep/DevCleanerView.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct DevCleanerView: View {
1010
@State private var cleanedBytes: Int64 = 0
1111
@State private var showCleanSheet = false
1212
@State private var showResultSheet = false
13+
@State private var showFDAAlert = false
1314
@State private var showNodeModulesSheet = false
1415
@State private var showBuildArtifactsSheet = false
1516
@State private var showCLIToolsSheet = false
@@ -451,7 +452,11 @@ struct DevCleanerView: View {
451452
isCleaning = true
452453
cleanedBytes = await devEngine.cleanSelected()
453454
isCleaning = false
454-
showResultSheet = true
455+
if cleanedBytes == 0 {
456+
showFDAAlert = true
457+
} else {
458+
showResultSheet = true
459+
}
455460
}
456461
}
457462
Button("Cancel", role: .cancel) {}
@@ -461,9 +466,16 @@ struct DevCleanerView: View {
461466
.sheet(isPresented: $showResultSheet) {
462467
DevCleanResultSheet(cleanedBytes: cleanedBytes) {
463468
showResultSheet = false
464-
devEngine.scanAll()
465469
}
466470
}
471+
.alert("Full Disk Access Required", isPresented: $showFDAAlert) {
472+
Button("Open System Settings") {
473+
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!)
474+
}
475+
Button("Cancel", role: .cancel) { }
476+
} message: {
477+
Text("MacSweep needs Full Disk Access to delete dev caches.\n\nGo to System Settings → Privacy & Security → Full Disk Access and enable MacSweep.")
478+
}
467479
}
468480
.padding(.horizontal, 20)
469481
.padding(.vertical, 12)

MacSweep/MacSweepApp.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
133133

134134
guard needsResize else { return }
135135

136+
// Position window in the center of the screen
136137
let targetRect = NSRect(
137138
x: visible.midX - targetWidth / 2,
138139
y: visible.midY - targetHeight / 2,

MacSweep/MenuBarView.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ struct MenuBarView: View {
133133
}
134134
.frame(width: 840)
135135
.background(bgGradient)
136+
.background(MenuBarPopupPositioner())
136137
.animation(.easeInOut(duration: 0.22), value: settings.menuBarTab)
137138
.onReceive(Timer.publish(every: 3.0, on: .main, in: .common).autoconnect()) { _ in
138139
// Keep menu popup values fresh while it is open.
@@ -3114,3 +3115,52 @@ struct MenuBarStatusRow: View {
31143115
}
31153116
}
31163117
}
3118+
3119+
// MARK: - Menu Bar Popup Positioner
3120+
// Moves the MenuBarExtra popup window to the top-right corner every time it opens.
3121+
private struct MenuBarPopupPositioner: NSViewRepresentable {
3122+
func makeNSView(context: Context) -> NSView {
3123+
let view = _PositionerView()
3124+
return view
3125+
}
3126+
func updateNSView(_ nsView: NSView, context: Context) {}
3127+
}
3128+
3129+
private final class _PositionerView: NSView {
3130+
private var observer: NSObjectProtocol?
3131+
3132+
override func viewDidMoveToWindow() {
3133+
super.viewDidMoveToWindow()
3134+
guard let window else { return }
3135+
positionTopRight(window)
3136+
3137+
// Re-position every time the popup becomes key (i.e. every click on the icon)
3138+
observer = NotificationCenter.default.addObserver(
3139+
forName: NSWindow.didBecomeKeyNotification,
3140+
object: window,
3141+
queue: .main
3142+
) { [weak window] _ in
3143+
guard let window else { return }
3144+
self.positionTopRight(window)
3145+
}
3146+
}
3147+
3148+
override func removeFromSuperview() {
3149+
if let observer { NotificationCenter.default.removeObserver(observer) }
3150+
super.removeFromSuperview()
3151+
}
3152+
3153+
private func positionTopRight(_ window: NSWindow) {
3154+
for delay in [0.05, 0.15, 0.3] {
3155+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak window] in
3156+
guard let window, window.isVisible else { return }
3157+
guard let screen = window.screen ?? NSScreen.main else { return }
3158+
let visible = screen.visibleFrame
3159+
let size = window.frame.size
3160+
let x = visible.maxX - size.width - 8
3161+
let y = visible.maxY - size.height
3162+
window.setFrameOrigin(NSPoint(x: x, y: y))
3163+
}
3164+
}
3165+
}
3166+
}

MacSweep/PerformanceManagerView.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ struct PerformanceManagerView: View {
88
@State private var selectedGroup: PerformanceGroup.Kind? = .loginItems
99
@State private var showResult = false
1010
@State private var resultMessage = ""
11+
@State private var showFDAAlert = false
1112

1213
@EnvironmentObject var navManager: NavigationManager
1314

@@ -62,10 +63,18 @@ struct PerformanceManagerView: View {
6263
}
6364
.background(DS.bg)
6465
.alert("Action Complete", isPresented: $showResult) {
65-
Button("OK") { engine.scanAll() }
66+
Button("OK") { }
6667
} message: {
6768
Text(resultMessage)
6869
}
70+
.alert("Full Disk Access Required", isPresented: $showFDAAlert) {
71+
Button("Open System Settings") {
72+
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!)
73+
}
74+
Button("Cancel", role: .cancel) { }
75+
} message: {
76+
Text("MacSweep needs Full Disk Access to manage startup items.\n\nGo to System Settings → Privacy & Security → Full Disk Access and enable MacSweep.")
77+
}
6978
}
7079

7180
// MARK: - Landing

MacSweep/PrivacyView.swift

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ struct PrivacyView: View {
55
@ObservedObject var cleanEngine: CleanEngine
66
@State private var showConfirm = false
77
@State private var showResult = false
8+
@State private var showFDAAlert = false
89
@State private var isScanning = false
910
@State private var privacyItems: [PrivacyItem] = []
1011
@State private var scanDone = false
@@ -44,6 +45,14 @@ struct PrivacyView: View {
4445
} message: {
4546
Text("This will clear \(ByteCountFormatter.string(fromByteCount: selectedSize, countStyle: .file)) of privacy-sensitive data.")
4647
}
48+
.alert("Full Disk Access Required", isPresented: $showFDAAlert) {
49+
Button("Open System Settings") {
50+
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!)
51+
}
52+
Button("Cancel", role: .cancel) { }
53+
} message: {
54+
Text("MacSweep needs Full Disk Access to clear privacy data.\n\nGo to System Settings → Privacy & Security → Full Disk Access and enable MacSweep.")
55+
}
4756
}
4857

4958
// MARK: - Scanning View
@@ -230,17 +239,25 @@ struct PrivacyView: View {
230239
}
231240

232241
private func clearPrivacyData() {
233-
let bytesToClear = selectedSize
234242
let fm = FileManager.default
243+
var actualCleared: Int64 = 0
235244
for item in privacyItems where item.isSelected {
236245
let url = URL(fileURLWithPath: item.path)
237-
if (try? fm.trashItem(at: url, resultingItemURL: nil)) == nil {
238-
try? fm.removeItem(at: url)
246+
let deleted: Bool
247+
if (try? fm.trashItem(at: url, resultingItemURL: nil)) != nil {
248+
deleted = true
249+
} else if (try? fm.removeItem(at: url)) != nil {
250+
deleted = true
251+
} else {
252+
deleted = false
239253
}
254+
if deleted { actualCleared += item.size }
240255
}
241-
if bytesToClear > 0 {
242-
scanEngine.recordFreed(bytes: bytesToClear, description: "Privacy cleanup")
256+
if actualCleared == 0 {
257+
showFDAAlert = true
258+
return
243259
}
260+
scanEngine.recordFreed(bytes: actualCleared, description: "Privacy cleanup")
244261
DS.playCleanComplete()
245262
scanPrivacy()
246263
}

0 commit comments

Comments
 (0)