diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index 31ba752..f445577 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -535,7 +535,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; ENABLE_HARDENED_RUNTIME = YES; @@ -552,7 +552,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.5; + MARKETING_VERSION = 1.6; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -569,7 +569,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BK8443AXLU; ENABLE_HARDENED_RUNTIME = YES; @@ -586,7 +586,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.5; + MARKETING_VERSION = 1.6; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index 3ce80f6..028fc78 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -79,17 +79,42 @@ struct LabeledDivider: View { struct AnimatedSearchStyle: TextFieldStyle { @State private var isHovered = false @Binding var text: String - + @Binding var reload: Bool + @EnvironmentObject var appState: AppState + func _body(configuration: TextField) -> some View { HStack(spacing: 5) { if isHovered || !text.isEmpty { + configuration - .frame(width: 160) +// .frame(minWidth: 130) .frame(height: 15) .font(.title3) .textFieldStyle(PlainTextFieldStyle()) + .padding(.leading, 50) + + + Image(systemName: "arrow.triangle.2.circlepath") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 15, height: 15) + .padding(.trailing, 5) + .foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5)) + .onTapGesture { + withAnimation { + // Refresh Apps list + reload.toggle() + let sortedApps = getSortedApps() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + appState.sortedApps.userApps = sortedApps.userApps + appState.sortedApps.systemApps = sortedApps.systemApps + reload.toggle() + } + } + + } } else { Image(systemName: "magnifyingglass") @@ -106,6 +131,7 @@ struct AnimatedSearchStyle: TextFieldStyle { RoundedRectangle(cornerRadius: 6) .strokeBorder(Color("mode").opacity(0.4), lineWidth: 1) .allowsHitTesting(false) + .padding(.leading, 50) } } ) @@ -152,14 +178,17 @@ struct SimpleSearchStyle: TextFieldStyle { .padding(.trailing, 5) .foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5)) .onTapGesture { - // Refresh Apps list - reload.toggle() - let sortedApps = getSortedApps() - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - appState.sortedApps.userApps = sortedApps.userApps - appState.sortedApps.systemApps = sortedApps.systemApps + withAnimation { + // Refresh Apps list reload.toggle() + let sortedApps = getSortedApps() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + appState.sortedApps.userApps = sortedApps.userApps + appState.sortedApps.systemApps = sortedApps.systemApps + reload.toggle() + } } + } // if trash { diff --git a/Pearcleaner/Logic/Utilities.swift b/Pearcleaner/Logic/Utilities.swift index 957c85a..6a2bcda 100644 --- a/Pearcleaner/Logic/Utilities.swift +++ b/Pearcleaner/Logic/Utilities.swift @@ -66,6 +66,7 @@ func checkFullDiskAccessForApp() -> Bool { // Check for access to Full Disk access func checkAndRequestFullDiskAccess(appState: AppState, skipAlert: Bool = false) -> Bool { @AppStorage("settings.permissions.disk") var diskP: Bool = false + @AppStorage("settings.permissions.hasLaunched") var hasLaunched: Bool = false let process = Process() process.launchPath = "/usr/bin/sqlite3" @@ -137,6 +138,14 @@ func checkAndRequestAccessibilityAccess(appState: AppState) -> Bool { } +// Check if app is installed in /Applications directory +func isAppInApplicationsDir() -> Bool { + if let bundlePath = Bundle.main.bundlePath as NSString? { + return bundlePath.deletingLastPathComponent == "/Applications" + } + return false +} + // Check if appearance is dark mode func isDarkModeEnabled() -> Bool { let process = Process() diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index 7042670..49395f8 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -15,6 +15,7 @@ struct PearcleanerApp: App { @AppStorage("settings.updater.updateTimeframe") private var updateTimeframe: Int = 1 @AppStorage("settings.permissions.disk") private var diskP: Bool = false @AppStorage("settings.permissions.events") private var diskE: Bool = false + @AppStorage("settings.permissions.hasLaunched") private var hasLaunched: Bool = false @AppStorage("displayMode") var displayMode: DisplayMode = .system @AppStorage("settings.general.mini") private var mini: Bool = false @State private var search = "" @@ -53,13 +54,16 @@ struct PearcleanerApp: App { // Make sure App Support folder exists in the future if needed for storage ensureApplicationSupportFolderExists(appState: appState) - // Check for updates - if diskP && diskE { + // Check for updates 1 minute after app launch + if diskP { loadGithubReleases(appState: appState) } - - // Check for disk/accessibility permissions - _ = checkAndRequestFullDiskAccess(appState: appState) + + // Check for disk/accessibility permissions just once on initial app launch + if !hasLaunched { + _ = checkAndRequestFullDiskAccess(appState: appState) + hasLaunched = true + } // TIMERS //////////////////////////////////////////////////////////////////////////////////// diff --git a/Pearcleaner/Settings/Update.swift b/Pearcleaner/Settings/Update.swift index 6783f8e..c586e33 100644 --- a/Pearcleaner/Settings/Update.swift +++ b/Pearcleaner/Settings/Update.swift @@ -115,7 +115,7 @@ func checkForUpdate(appState: AppState, manual: Bool = false) { guard let latestRelease = appState.releases.first else { return } let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String if latestRelease.tag_name > currentVersion ?? "" { - NewWin.show(appState: appState, width: 500, height: 400, newWin: .update) + NewWin.show(appState: appState, width: 500, height: 440, newWin: .update) // appState.alertType = .update // appState.showAlert = true } else { diff --git a/Pearcleaner/Views/FilesView.swift b/Pearcleaner/Views/FilesView.swift index 02fdc8b..7203ba4 100644 --- a/Pearcleaner/Views/FilesView.swift +++ b/Pearcleaner/Views/FilesView.swift @@ -91,24 +91,24 @@ struct FilesView: View { } if appState.appInfo.appName.count < 5 { HStack(alignment: .center) { - Button(""){ - showPop = true - } - .buttonStyle(SimpleButtonStyle(icon: "exclamationmark.triangle.fill", help: "Warning", color: .red)) - .popover(isPresented: $showPop, arrowEdge: .top) { - VStack() { - Text("Pearcleaner searches for files with a combination of bundle id and app name.\n**\(appState.appInfo.appName)** has a common or short app name so there might be unrelated files found.\nPlease check the list thoroughly before uninstalling.") - .padding(20) - .font(.title2) + Image(systemName: "exclamationmark.triangle.fill") + .foregroundStyle(.red) + .popover(isPresented: $showPop, arrowEdge: .top) { + VStack() { + Text("Pearcleaner searches for files via a combination of bundle id and app name.\n**\(appState.appInfo.appName)** has a common or short app name so there might be unrelated files found.\nPlease check the list thoroughly before uninstalling.") + .padding() + .font(.title2) + } + } - - } - Text("Read caution message") + + Text("Warning") .foregroundStyle(Color.red) Spacer() } .padding(.top) + .onTapGesture { showPop = true } diff --git a/Pearcleaner/Views/TopBarMini.swift b/Pearcleaner/Views/TopBarMini.swift index 0701a87..9be7015 100644 --- a/Pearcleaner/Views/TopBarMini.swift +++ b/Pearcleaner/Views/TopBarMini.swift @@ -54,38 +54,38 @@ struct TopBarMini: View { if appState.currentView == .apps { Button("") { withAnimation(.easeInOut(duration: 0.5)) { - updateOnMain { +// updateOnMain { appState.currentView = .empty appState.appInfo = AppInfo.empty - } +// } } } .buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode"))) } else if appState.currentView == .empty { Button("") { withAnimation(.easeInOut(duration: 0.5)) { - updateOnMain { +// updateOnMain { appState.currentView = .apps - } +// } } } .buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps", color: Color("mode"))) } else if appState.currentView == .files { Button("") { withAnimation(.easeInOut(duration: 0.5)) { - updateOnMain { +// updateOnMain { appState.currentView = .empty appState.appInfo = AppInfo.empty - } +// } } } .buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode"))) Button("") { withAnimation(.easeInOut(duration: 0.5)) { - updateOnMain { +// updateOnMain { appState.currentView = .apps - } +// } } } .buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps", color: Color("mode"))) @@ -107,7 +107,7 @@ struct SearchBarMini: View { var body: some View { HStack { TextField("Search", text: $search) - .textFieldStyle(AnimatedSearchStyle(text: $search)) + .textFieldStyle(AnimatedSearchStyle(text: $search, reload: $reload)) // .textFieldStyle(SimpleSearchStyle(trash: true, reload: $reload, text: $search)) } // .frame(height: 20) diff --git a/Pearcleaner/Windows/UpdateView.swift b/Pearcleaner/Windows/UpdateView.swift index c2c9194..c85dd85 100644 --- a/Pearcleaner/Windows/UpdateView.swift +++ b/Pearcleaner/Windows/UpdateView.swift @@ -10,6 +10,7 @@ import SwiftUI struct UpdateView: View { @EnvironmentObject var appState: AppState + let isAppInAppsDir = isAppInApplicationsDir() var body: some View { VStack { @@ -47,6 +48,18 @@ struct UpdateView: View { Spacer() + if !isAppInAppsDir { + HStack { + Text("Pearcleaner not in /Applications directory. Updater could have permission issues updating files.") + .font(.footnote) + .foregroundStyle(.red) + .padding() + .lineLimit(2) + .fixedSize(horizontal: false, vertical: true) + } + + + } VStack() { ProgressView("\(appState.progressBar.0)", value: appState.progressBar.1, total: 1.0)