Skip to content

Commit 73b40fa

Browse files
authored
Merge pull request #1 from Prot10/fix/code-review-improvements
Fix/code review improvements
2 parents 7bd6898 + ecee270 commit 73b40fa

36 files changed

+3063
-425
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ npm-debug.log*
3636
.env
3737
.env.local
3838
.env.*.local
39+
40+
# Profiling
41+
*.profraw
42+
*.profdata

CLAUDE.md

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ MyMacCleaner/
7878
│ │ ├── HomebrewService.swift
7979
│ │ └── LocalizationManager.swift
8080
│ ├── Models/
81-
│ │ └── ScanResult.swift
81+
│ │ ├── ScanResult.swift
82+
│ │ └── PermissionCategory.swift
8283
│ └── Extensions/
8384
├── Features/
8485
│ ├── Home/
@@ -98,7 +99,14 @@ MyMacCleaner/
9899
│ ├── Performance/
99100
│ ├── Applications/
100101
│ ├── PortManagement/
101-
│ └── SystemHealth/
102+
│ ├── SystemHealth/
103+
│ ├── StartupItems/
104+
│ └── Permissions/
105+
│ ├── PermissionsView.swift
106+
│ ├── PermissionsViewModel.swift
107+
│ └── Components/
108+
│ ├── PermissionCategoryCard.swift
109+
│ └── PermissionFolderRow.swift
102110
├── Resources/
103111
│ └── Assets.xcassets/
104112
└── MyMacCleaner.entitlements
@@ -831,6 +839,90 @@ MyMacCleaner/
831839

832840
---
833841

842+
### 2026-01-05 - Permissions Management Page & Warning Fixes
843+
844+
**Session Goal**: Add new Permissions page for reviewing and managing folder access permissions, fix all compiler warnings
845+
846+
**Completed**:
847+
848+
**Permissions Management Page:**
849+
- Created PermissionCategory.swift data models:
850+
- PermissionCategoryType enum (fullDiskAccess, userFolders, systemFolders, applicationData, startupPaths)
851+
- FolderAccessInfo struct (path, displayName, status, requiresFDA, canTriggerTCCDialog)
852+
- FolderAccessStatus enum (accessible, denied, notExists, checking)
853+
- PermissionCategoryState struct for UI state
854+
- Created PermissionsViewModel.swift:
855+
- buildCategories() creates all 5 category states with folder paths
856+
- checkAllPermissions() tests actual read access to each folder
857+
- requestFolderAccess() triggers TCC dialog for user folders
858+
- revokeFolderAccess() opens appropriate System Settings pane
859+
- Auto-refresh on app activation
860+
- Created PermissionFolderRow.swift component:
861+
- Status icon (green checkmark / red X / orange ?)
862+
- Folder displayName and path
863+
- "Grant" button (green for TCC, blue gear for FDA)
864+
- "Revoke" button (red, opens System Settings)
865+
- Created PermissionCategoryCard.swift:
866+
- Expandable glassCard with header showing category status
867+
- Lists all folders with PermissionFolderRow
868+
- Created PermissionsView.swift:
869+
- Header with title, subtitle, and refresh button
870+
- Summary card showing X/Y folders accessible
871+
- Expandable category cards
872+
- Auto-refresh on NSApplication.didBecomeActiveNotification
873+
- Updated ContentView.swift:
874+
- Added NavigationSection.permissions case
875+
- Icon: "lock.shield.fill", Color: Theme.Colors.permissions (indigo)
876+
- Updated AppState.swift:
877+
- Added permissionsViewModel property
878+
- Updated Theme.swift:
879+
- Added permissions color (indigo)
880+
- Updated Localizable.xcstrings:
881+
- Added 25+ translation keys (EN/IT/ES) for permissions UI
882+
883+
**Warning Fixes:**
884+
- Fixed AuthorizationService.swift:73,91 - "capture of 'self' with non-Sendable type"
885+
- Moved escapeForAppleScript() call before async block to capture escaped string instead of self
886+
- Fixed SpaceLensViewModel.swift:178 - "makeIterator unavailable from async"
887+
- Changed `for case let fileURL as URL in enumerator` to `while let fileURL = enumerator.nextObject() as? URL`
888+
- Fixed PortManagementViewModel.swift:148 - "call to actor-isolated method"
889+
- Made parseLsofOutput() and parseAddressPort() nonisolated functions
890+
- Fixed FileScanner.swift:211,298 - "makeIterator unavailable from async"
891+
- Changed both for-in loops to while-let pattern
892+
- Fixed AppUpdateChecker.swift:95 - "reference to captured var"
893+
- Used collected.count instead of mutable captured variable
894+
- Fixed StartupItemsService.swift:180,520 - "call to actor-isolated method"
895+
- Reordered modifiers to `private static nonisolated func` for parseBTMOutput and getCodeSigningTeam
896+
897+
**Files Created**:
898+
- `MyMacCleaner/Core/Models/PermissionCategory.swift`
899+
- `MyMacCleaner/Features/Permissions/PermissionsView.swift`
900+
- `MyMacCleaner/Features/Permissions/PermissionsViewModel.swift`
901+
- `MyMacCleaner/Features/Permissions/Components/PermissionCategoryCard.swift`
902+
- `MyMacCleaner/Features/Permissions/Components/PermissionFolderRow.swift`
903+
904+
**Files Modified**:
905+
- `MyMacCleaner/App/ContentView.swift` (added permissions navigation)
906+
- `MyMacCleaner/App/AppState.swift` (added permissionsViewModel)
907+
- `MyMacCleaner/Core/Design/Theme.swift` (added permissions color)
908+
- `MyMacCleaner/Core/Services/AuthorizationService.swift` (fixed self capture warning)
909+
- `MyMacCleaner/Core/Services/FileScanner.swift` (fixed makeIterator warning)
910+
- `MyMacCleaner/Core/Services/AppUpdateChecker.swift` (fixed captured var warning)
911+
- `MyMacCleaner/Core/Services/StartupItemsService.swift` (fixed actor-isolated warnings)
912+
- `MyMacCleaner/Features/SpaceLens/SpaceLensViewModel.swift` (fixed makeIterator warning)
913+
- `MyMacCleaner/Features/PortManagement/PortManagementViewModel.swift` (fixed actor-isolated warning)
914+
- `MyMacCleaner/Resources/Localizable.xcstrings` (added permissions translations)
915+
916+
**Key Technical Decisions**:
917+
- TCC folders (Downloads, Documents, Desktop) trigger system permission dialog via FileManager.contentsOfDirectory()
918+
- FDA folders cannot trigger dialog programmatically - show "Open Settings" button
919+
- Permission revocation not possible programmatically on macOS - opens appropriate System Settings pane
920+
- Auto-refresh permissions when app becomes active (user returns from System Settings)
921+
922+
**Build Status**: SUCCESS (0 warnings)
923+
924+
---
925+
834926
## Constraints & Guidelines
835927

836928
### Code Quality

MyMacCleaner.xcodeproj/project.pbxproj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,22 @@
2929
650DE2B32EE843C200C84519 /* MyMacCleanerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MyMacCleanerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3030
/* End PBXFileReference section */
3131

32+
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
33+
650DE29E2EE843C000C84520 /* Exceptions for "MyMacCleaner" folder in "MyMacCleaner" target */ = {
34+
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
35+
membershipExceptions = (
36+
Info.plist,
37+
);
38+
target = 650DE29A2EE843C000C84519 /* MyMacCleaner */;
39+
};
40+
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
41+
3242
/* Begin PBXFileSystemSynchronizedRootGroup section */
3343
650DE29D2EE843C000C84519 /* MyMacCleaner */ = {
3444
isa = PBXFileSystemSynchronizedRootGroup;
45+
exceptions = (
46+
650DE29E2EE843C000C84520 /* Exceptions for "MyMacCleaner" folder in "MyMacCleaner" target */,
47+
);
3548
path = MyMacCleaner;
3649
sourceTree = "<group>";
3750
};

MyMacCleaner/App/ContentView.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ enum NavigationSection: String, CaseIterable, Identifiable {
4848
case startupItems
4949
case portManagement
5050
case systemHealth
51+
case permissions
5152

5253
var id: String { rawValue }
5354

@@ -60,6 +61,7 @@ enum NavigationSection: String, CaseIterable, Identifiable {
6061
case .startupItems: return "power.circle.fill"
6162
case .portManagement: return "network"
6263
case .systemHealth: return "heart.text.square.fill"
64+
case .permissions: return "lock.shield.fill"
6365
}
6466
}
6567

@@ -80,6 +82,7 @@ enum NavigationSection: String, CaseIterable, Identifiable {
8082
case .startupItems: return Theme.Colors.startup // Yellow
8183
case .portManagement: return Theme.Colors.ports // Cyan
8284
case .systemHealth: return Theme.Colors.health // Red
85+
case .permissions: return Theme.Colors.permissions // Indigo
8386
}
8487
}
8588
}
@@ -365,6 +368,8 @@ struct DetailContentView: View {
365368
PortManagementView(viewModel: appState.portManagementViewModel)
366369
case .systemHealth:
367370
SystemHealthView(viewModel: appState.systemHealthViewModel)
371+
case .permissions:
372+
PermissionsView(viewModel: appState.permissionsViewModel)
368373
}
369374
}
370375
}

MyMacCleaner/App/MyMacCleanerApp.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ struct PermissionsSettingsView: View {
183183
}
184184

185185
struct AboutSettingsView: View {
186+
private var appVersion: String {
187+
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
188+
let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1"
189+
return "\(version) (\(build))"
190+
}
191+
186192
var body: some View {
187193
VStack(spacing: 16) {
188194
Image(systemName: "sparkles")
@@ -192,7 +198,7 @@ struct AboutSettingsView: View {
192198
Text("MyMacCleaner")
193199
.font(.title.bold())
194200

195-
Text("Version 1.0.0")
201+
Text("Version \(appVersion)")
196202
.foregroundStyle(.secondary)
197203

198204
Text(String(localized: "settings.aboutDescription"))

MyMacCleaner/Core/Design/Theme.swift

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@ enum Theme {
3535
static let error = Color.red
3636
static let info = Color.blue
3737

38-
// Section colors (7 distinct colors)
38+
// Section colors (8 distinct colors)
3939
static let home = Color.blue // Blue
4040
static let storage = Color.orange // Orange
4141
static let memory = Color.purple // Purple
4242
static let apps = Color.green // Green
4343
static let startup = Color.yellow // Yellow
4444
static let ports = Color.cyan // Cyan / Light Blue
4545
static let health = Color.red // Red
46+
static let permissions = Color.indigo // Indigo
4647
}
4748

4849
// MARK: - Typography
@@ -105,6 +106,56 @@ enum Theme {
105106
static let springBouncy = SwiftUI.Animation.spring(response: 0.5, dampingFraction: 0.6)
106107
static let springSmooth = SwiftUI.Animation.spring(response: 0.6, dampingFraction: 0.85)
107108
}
109+
110+
// MARK: - Thresholds & Constants
111+
112+
enum Thresholds {
113+
/// Minimum file size to include in scans (1 KB)
114+
static let minimumFileSize: Int64 = 1024
115+
116+
/// Disk space thresholds for health warnings
117+
enum DiskSpace {
118+
/// Below this is critical (20 GB)
119+
static let criticalFreeSpace: Int64 = 20 * 1024 * 1024 * 1024
120+
/// Below this is warning (50 GB)
121+
static let warningFreeSpace: Int64 = 50 * 1024 * 1024 * 1024
122+
}
123+
124+
/// Startup items thresholds for health score
125+
enum StartupItems {
126+
/// Above this is warning
127+
static let warningCount = 10
128+
/// Above this is critical
129+
static let criticalCount = 20
130+
}
131+
132+
/// Memory usage thresholds (percentage)
133+
enum Memory {
134+
/// Above this is warning (80%)
135+
static let warningUsage: Double = 0.80
136+
/// Above this is critical (90%)
137+
static let criticalUsage: Double = 0.90
138+
}
139+
}
140+
141+
// MARK: - Timing Constants
142+
143+
enum Timing {
144+
/// Short delay for visual feedback (50ms)
145+
static let visualFeedback: UInt64 = 50_000_000
146+
/// Delay for progress display (80ms)
147+
static let progressStep: UInt64 = 80_000_000
148+
/// Medium delay (200ms)
149+
static let shortPause: UInt64 = 200_000_000
150+
/// Delay to show completion (300ms)
151+
static let completionDisplay: UInt64 = 300_000_000
152+
/// Auto-dismiss toast (3s)
153+
static let toastDuration: UInt64 = 3_000_000_000
154+
/// Clear results delay (5s)
155+
static let clearResultsDelay: UInt64 = 5_000_000_000
156+
/// Process refresh interval (seconds)
157+
static let processRefreshInterval: TimeInterval = 2.0
158+
}
108159
}
109160

110161
// MARK: - Shadow Style

MyMacCleaner/Core/Design/ToastView.swift

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,45 @@
11
import SwiftUI
22

3-
// MARK: - Toast View
4-
5-
struct ToastView: View {
6-
let message: String
7-
let type: HomeViewModel.ToastType
8-
let onDismiss: () -> Void
3+
// MARK: - Shared Toast Type
94

10-
@State private var isVisible = false
5+
/// Shared toast notification type used across all ViewModels
6+
/// Consolidates the previously duplicated ToastType enums
7+
enum ToastType: Sendable {
8+
case success
9+
case error
10+
case info
1111

1212
var icon: String {
13-
switch type {
13+
switch self {
1414
case .success: return "checkmark.circle.fill"
1515
case .error: return "xmark.circle.fill"
1616
case .info: return "info.circle.fill"
1717
}
1818
}
1919

20-
var iconColor: Color {
21-
switch type {
20+
var color: Color {
21+
switch self {
2222
case .success: return .green
2323
case .error: return .red
2424
case .info: return .blue
2525
}
2626
}
27+
}
28+
29+
// MARK: - Toast View
30+
31+
struct ToastView: View {
32+
let message: String
33+
let type: ToastType
34+
let onDismiss: () -> Void
35+
36+
@State private var isVisible = false
2737

2838
var body: some View {
2939
HStack(spacing: Theme.Spacing.sm) {
30-
Image(systemName: icon)
40+
Image(systemName: type.icon)
3141
.font(.title3)
32-
.foregroundStyle(iconColor)
42+
.foregroundStyle(type.color)
3343

3444
Text(message)
3545
.font(Theme.Typography.subheadline)
@@ -52,7 +62,7 @@ struct ToastView: View {
5262
)
5363
.overlay(
5464
RoundedRectangle(cornerRadius: Theme.CornerRadius.large)
55-
.strokeBorder(iconColor.opacity(0.3), lineWidth: 1)
65+
.strokeBorder(type.color.opacity(0.3), lineWidth: 1)
5666
)
5767
.shadow(color: .black.opacity(0.15), radius: 20, y: 10)
5868
.opacity(isVisible ? 1 : 0)

0 commit comments

Comments
 (0)