This commit is contained in:
Alin
2023-11-14 13:41:21 -07:00
parent 46125d8cbc
commit e3fe9ad281
21 changed files with 673 additions and 195 deletions
+12 -4
View File
@@ -7,6 +7,8 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A272B03E71D00376976 /* MiniMode.swift */; };
C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7045A292B03FAF000376976 /* TopBarMini.swift */; };
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; }; C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893042AFD42E600C8C1CD /* DeepLink.swift */; };
C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; }; C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; };
C7575D382B0181FC006A600A /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7575D372B0181FC006A600A /* FileWatcher */; }; C7575D382B0181FC006A600A /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7575D372B0181FC006A600A /* FileWatcher */; };
@@ -59,6 +61,8 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
C7045A272B03E71D00376976 /* MiniMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniMode.swift; sourceTree = "<group>"; };
C7045A292B03FAF000376976 /* TopBarMini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBarMini.swift; sourceTree = "<group>"; };
C72893042AFD42E600C8C1CD /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; }; C72893042AFD42E600C8C1CD /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PearcleanerSentinel; sourceTree = BUILT_PRODUCTS_DIR; }; C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = PearcleanerSentinel; sourceTree = BUILT_PRODUCTS_DIR; };
C72893112AFD51EA00C8C1CD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; }; C72893112AFD51EA00C8C1CD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
@@ -206,9 +210,11 @@
C77B90222AF2D616009CC655 /* FilesView.swift */, C77B90222AF2D616009CC655 /* FilesView.swift */,
C76D08542AF89CDE00D07867 /* AppListView.swift */, C76D08542AF89CDE00D07867 /* AppListView.swift */,
C7D31D472AFEB23700C7ED9E /* TopBar.swift */, C7D31D472AFEB23700C7ED9E /* TopBar.swift */,
C7045A292B03FAF000376976 /* TopBarMini.swift */,
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */, C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
C7D31D522B0048BE00C7ED9E /* TrashView.swift */, C7D31D522B0048BE00C7ED9E /* TrashView.swift */,
C7D31D582B00A49300C7ED9E /* DropTarget.swift */, C7D31D582B00A49300C7ED9E /* DropTarget.swift */,
C7045A272B03E71D00376976 /* MiniMode.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -336,7 +342,9 @@
C7D31D462AFDA90C00C7ED9E /* Sentinel.swift in Sources */, C7D31D462AFDA90C00C7ED9E /* Sentinel.swift in Sources */,
C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */, C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */,
C77B90062AF18E2E009CC655 /* ContentView.swift in Sources */, C77B90062AF18E2E009CC655 /* ContentView.swift in Sources */,
C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */,
C77B901C2AF193A3009CC655 /* Styles.swift in Sources */, C77B901C2AF193A3009CC655 /* Styles.swift in Sources */,
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */,
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */, C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */,
C76D084C2AF83C9D00D07867 /* Permissions.swift in Sources */, C76D084C2AF83C9D00D07867 /* Permissions.swift in Sources */,
C76D084A2AF83C6E00D07867 /* General.swift in Sources */, C76D084A2AF83C6E00D07867 /* General.swift in Sources */,
@@ -517,7 +525,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU; DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -534,7 +542,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.1; MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -551,7 +559,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU; DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -568,7 +576,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.1; MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner; PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
+8
View File
@@ -19,6 +19,7 @@ class AppState: ObservableObject
@Published var selectedItems = Set<URL>() @Published var selectedItems = Set<URL>()
@Published var alertType = AlertType.off @Published var alertType = AlertType.off
@Published var currentView = CurrentDetailsView.empty @Published var currentView = CurrentDetailsView.empty
// @Published var currentMode = CurrentMode.regular
@Published var showAlert: Bool = false @Published var showAlert: Bool = false
@Published var sidebar: Bool = true @Published var sidebar: Bool = true
@Published var isReminderVisible: Bool = false @Published var isReminderVisible: Bool = false
@@ -74,8 +75,15 @@ enum CurrentDetailsView:Int
{ {
case empty case empty
case files case files
case apps
} }
//enum CurrentMode:Int
//{
// case regular
// case mini
//}
enum AlertType:Int enum AlertType:Int
{ {
case diskAccess case diskAccess
+50 -17
View File
@@ -17,25 +17,58 @@ class DeeplinkManager {
} }
func manage(url: URL, appState: AppState) { func manage(url: URL, appState: AppState) {
guard url.scheme == DeepLinkConstants.scheme, if url.pathExtension == "app" {
url.host == DeepLinkConstants.host, handleAppBundle(url: url, appState: appState)
let components = URLComponents(url: url, resolvingAgainstBaseURL: true), } else if url.scheme == DeepLinkConstants.scheme,
let queryItems = components.queryItems url.host == DeepLinkConstants.host,
else { let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
print("URL does not match the expected scheme and host") let queryItems = components.queryItems {
return if let path = queryItems.first(where: { $0.name == DeepLinkConstants.query })?.value {
} let pathURL = URL(fileURLWithPath: path)
let appInfo = getAppInfo(atPath: pathURL)
if let path = queryItems.first(where: { $0.name == DeepLinkConstants.query })?.value { updateOnMain {
let pathURL = URL(fileURLWithPath: path) appState.appInfo = appInfo!
let appInfo = getAppInfo(atPath: pathURL) findPathsForApp(appState: appState, appInfo: appState.appInfo)
updateOnMain { appState.currentView = .files
appState.appInfo = appInfo! }
findPathsForApp(appState: appState, appInfo: appState.appInfo) } else {
appState.currentView = .files print("No path query parameter found in the URL")
} }
} else { } else {
print("No path query parameter found in the URL") print("URL does not match the expected scheme and host")
} }
} }
// func manage(url: URL, appState: AppState) {
// guard url.scheme == DeepLinkConstants.scheme,
// url.host == DeepLinkConstants.host,
// let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
// let queryItems = components.queryItems
// else {
// print("URL does not match the expected scheme and host")
// return
// }
//
// if let path = queryItems.first(where: { $0.name == DeepLinkConstants.query })?.value {
// let pathURL = URL(fileURLWithPath: path)
// let appInfo = getAppInfo(atPath: pathURL)
// updateOnMain {
// appState.appInfo = appInfo!
// findPathsForApp(appState: appState, appInfo: appState.appInfo)
// appState.currentView = .files
// }
// } else {
// print("No path query parameter found in the URL")
// }
// }
func handleAppBundle(url: URL, appState: AppState) {
let appInfo = getAppInfo(atPath: url)
updateOnMain {
appState.appInfo = appInfo!
findPathsForApp(appState: appState, appInfo: appState.appInfo)
appState.currentView = .files
}
}
} }
+9 -9
View File
@@ -28,18 +28,18 @@ struct SimpleButtonStyle: ButtonStyle {
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 20) .frame(width: 20)
.foregroundColor(color) .foregroundColor(hovered ? color : color.opacity(0.5))
} }
.padding(8) .padding(8)
.background { // .background {
if hovered && !(shield ?? false) { // if hovered && !(shield ?? false) {
// Circle() //// Circle()
//// .strokeBorder(Color("AccentColor"), lineWidth: 1)
// RoundedRectangle(cornerRadius: 8)
// .strokeBorder(Color("AccentColor"), lineWidth: 1) // .strokeBorder(Color("AccentColor"), lineWidth: 1)
RoundedRectangle(cornerRadius: 8) // }
.strokeBorder(Color("AccentColor"), lineWidth: 1) //
} // }
}
.onHover { hovering in .onHover { hovering in
withAnimation() { withAnimation() {
hovered = hovering hovered = hovering
+13 -3
View File
@@ -24,6 +24,15 @@ func updateOnBackground(_ updates: @escaping () -> Void) {
} }
// Resize window
func resizeWindow() {
if let window = NSApplication.shared.windows.first {
let newSize = NSSize(width: 500, height: 450)
window.setContentSize(newSize)
}
}
// Check for access to Full Disk access // Check for access to Full Disk access
func checkAndRequestFullDiskAccess(appState: AppState, skipAlert: Bool = false) -> Bool { func checkAndRequestFullDiskAccess(appState: AppState, skipAlert: Bool = false) -> Bool {
@AppStorage("settings.permissions.disk") var diskP: Bool = false @AppStorage("settings.permissions.disk") var diskP: Bool = false
@@ -268,8 +277,9 @@ func presentAlert(appState: AppState) -> Alert {
switch appState.alertType { switch appState.alertType {
case .update: case .update:
return Alert(title: Text("Update Available 🥳"), message: Text("You may choose to install the update now, otherwise you may start the update later from Settings"), primaryButton: .default(Text("Install")) { return Alert(title: Text("Update Available 🥳"), message: Text("You may choose to install the update now, otherwise you may check again later from Settings"), primaryButton: .default(Text("Open")) {
downloadUpdate(appState: appState) // downloadUpdate(appState: appState)
launchUpdate()
appState.alertType = .off appState.alertType = .off
}, secondaryButton: .cancel()) }, secondaryButton: .cancel())
case .no_update: case .no_update:
@@ -278,7 +288,7 @@ func presentAlert(appState: AppState) -> Alert {
appState.alertType = .off appState.alertType = .off
}) })
case .diskAccess: case .diskAccess:
return Alert(title: Text("Permissions"), message: Text("This application requires Full Disk and Accessibility permissions. Once you grant full disk, don't restart the app yet. Wait a second for a second prompt for accessibility and then restart"), primaryButton: .default(Text("Allow in Settings")) { return Alert(title: Text("Permissions"), message: Text("This application requires Full Disk and Accessibility permissions. Drag the app into the Full Disk pane if not visible."), primaryButton: .default(Text("Allow in Settings")) {
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") { if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") {
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
} }
+95 -32
View File
@@ -16,54 +16,108 @@ struct PearcleanerApp: App {
@AppStorage("settings.permissions.disk") private var diskP: Bool = false @AppStorage("settings.permissions.disk") private var diskP: Bool = false
@AppStorage("settings.permissions.events") private var diskE: Bool = false @AppStorage("settings.permissions.events") private var diskE: Bool = false
@AppStorage("displayMode") var displayMode: DisplayMode = .dark @AppStorage("displayMode") var displayMode: DisplayMode = .dark
@AppStorage("settings.general.mini") private var mini: Bool = false
@State private var search = "" @State private var search = ""
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
AppListView(search: $search) if !mini {
.environmentObject(appState) AppListView(search: $search)
.frame(minWidth: 1020, minHeight: 700) .environmentObject(appState)
.preferredColorScheme(displayMode.colorScheme) .frame(minWidth: 1020, minHeight: 700)
.alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) } .preferredColorScheme(displayMode.colorScheme)
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*")) .alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) }
.onAppear { .handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
NSWindow.allowsAutomaticWindowTabbing = false .onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
// Get Apps // Get Apps
let sortedApps = getSortedApps() let sortedApps = getSortedApps()
appState.sortedApps.userApps = sortedApps.userApps appState.sortedApps.userApps = sortedApps.userApps
appState.sortedApps.systemApps = sortedApps.systemApps appState.sortedApps.systemApps = sortedApps.systemApps
Task { Task {
#if !DEBUG #if !DEBUG
// Make sure App Support folder exists in the future if needed for storage // Make sure App Support folder exists in the future if needed for storage
ensureApplicationSupportFolderExists(appState: appState) ensureApplicationSupportFolderExists(appState: appState)
let fileManager = FileManager.default
let destinationURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("Pearcleaner").appendingPathComponent("Pearcleaner.zip")
try? fileManager.removeItem(at: destinationURL)
// Check for updates // Check for updates
if diskP && diskE { if diskP && diskE {
loadGithubReleases(appState: appState)
}
// Check for disk/accessibility permissions
_ = checkAndRequestFullDiskAccess(appState: appState)
// TIMERS ////////////////////////////////////////////////////////////////////////////////////
// Check for app updates every 8 hours or whatever user saved setting. Also refresh autosuggestion list
let updateSeconds = updateTimeframe.daysToSeconds
_ = Timer.scheduledTimer(withTimeInterval: updateSeconds, repeats: true) { _ in
DispatchQueue.main.async {
loadGithubReleases(appState: appState) loadGithubReleases(appState: appState)
} }
}
// Check for disk/accessibility permissions
_ = checkAndRequestFullDiskAccess(appState: appState)
// TIMERS ////////////////////////////////////////////////////////////////////////////////////
// Check for app updates every 8 hours or whatever user saved setting. Also refresh autosuggestion list
let updateSeconds = updateTimeframe.daysToSeconds
_ = Timer.scheduledTimer(withTimeInterval: updateSeconds, repeats: true) { _ in
DispatchQueue.main.async {
loadGithubReleases(appState: appState)
}
}
#endif #endif
}
} }
} } else {
MiniMode(search: $search)
.environmentObject(appState)
.frame(minWidth: 500, minHeight: 450)
.preferredColorScheme(displayMode.colorScheme)
.alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) }
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
.onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
// Get Apps
let sortedApps = getSortedApps()
appState.sortedApps.userApps = sortedApps.userApps
appState.sortedApps.systemApps = sortedApps.systemApps
Task {
#if !DEBUG
// Make sure App Support folder exists in the future if needed for storage
ensureApplicationSupportFolderExists(appState: appState)
let fileManager = FileManager.default
let destinationURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("Pearcleaner").appendingPathComponent("Pearcleaner.zip")
try? fileManager.removeItem(at: destinationURL)
// Check for updates
if diskP && diskE {
loadGithubReleases(appState: appState)
}
// Check for disk/accessibility permissions
_ = checkAndRequestFullDiskAccess(appState: appState)
// TIMERS ////////////////////////////////////////////////////////////////////////////////////
// Check for app updates every 8 hours or whatever user saved setting. Also refresh autosuggestion list
let updateSeconds = updateTimeframe.daysToSeconds
_ = Timer.scheduledTimer(withTimeInterval: updateSeconds, repeats: true) { _ in
DispatchQueue.main.async {
loadGithubReleases(appState: appState)
}
}
#endif
}
}
}
} }
.windowStyle(.hiddenTitleBar) .windowStyle(.hiddenTitleBar)
.windowToolbarStyle(.unified) .windowToolbarStyle(.unified)
@@ -73,6 +127,7 @@ struct PearcleanerApp: App {
CommandGroup(replacing: .newItem, addition: { }) CommandGroup(replacing: .newItem, addition: { })
} }
Settings { Settings {
SettingsView() SettingsView()
.environmentObject(appState) .environmentObject(appState)
@@ -89,4 +144,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true return true
} }
// func application(_ sender: NSApplication, open urls: [URL]) {
// for url in urls {
// if url.pathExtension == "app" {
// print("Dropped app path:", url.path)
// }
// }
// }
} }
@@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "logo_combo.png", "filename" : "logo_combo_small.png",
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x" "scale" : "1x"
}, },
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "pear_small.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+15
View File
@@ -2,6 +2,21 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Application Bundle</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>com.apple.application-bundle</string>
</array>
</dict>
</array>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
<array> <array>
<dict> <dict>
+1 -1
View File
@@ -29,7 +29,7 @@ struct AboutSettingsTab: View {
.buttonStyle(SimpleButtonStyle(icon: "cloud", help: "Update application", color: Color("AccentColor"))) .buttonStyle(SimpleButtonStyle(icon: "cloud", help: "Update application", color: Color("AccentColor")))
Button("") { Button("") {
NSWorkspace.shared.open(URL(string: "https://github.com/ghosted-labs/Pearcleaner")!) NSWorkspace.shared.open(URL(string: "https://github.com/alienator88/Pearcleaner")!)
} }
.buttonStyle(SimpleButtonStyle(icon: "paperplane", help: "View repository", color: Color("AccentColor"))) .buttonStyle(SimpleButtonStyle(icon: "paperplane", help: "View repository", color: Color("AccentColor")))
} }
+45 -19
View File
@@ -11,7 +11,9 @@ import SwiftUI
struct GeneralSettingsTab: View { struct GeneralSettingsTab: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@AppStorage("settings.general.glass") private var glass: Bool = true @AppStorage("settings.general.glass") private var glass: Bool = true
// @AppStorage("settings.general.ants") private var ants: Bool = false @AppStorage("settings.general.mini") private var mini: Bool = false
@AppStorage("settings.general.dark") var isDark: Bool = true
@AppStorage("displayMode") var displayMode: DisplayMode = .dark
var body: some View { var body: some View {
Form { Form {
@@ -34,30 +36,54 @@ struct GeneralSettingsTab: View {
.fill(Color("mode").opacity(0.05)) .fill(Color("mode").opacity(0.05))
) )
// HStack { HStack {
// VStack(alignment: .leading, spacing: 5) { VStack(alignment: .leading, spacing: 5) {
// Text("Animation").font(.title2) Text("Dark Mode").font(.title2)
// Text("Toggles the marching ants animation on the drop zone") Text("Toggles between dark and light mode")
// .font(.footnote) .font(.footnote)
// .foregroundStyle(.gray) .foregroundStyle(.gray)
// } }
// Spacer() Spacer()
// Toggle(isOn: $ants, label: { Toggle(isOn: $isDark, label: {
// }) })
// .toggleStyle(.switch) .toggleStyle(.switch)
// } .onChange(of: isDark) { newValue in
// .padding() displayMode.colorScheme = newValue ? .dark : .light
// .background( }
// RoundedRectangle(cornerRadius: 8) }
// .fill(Color("mode").opacity(0.05)) .padding()
// ) .background(
RoundedRectangle(cornerRadius: 8)
.fill(Color("mode").opacity(0.05))
)
HStack {
VStack(alignment: .leading, spacing: 5) {
Text("Mini").font(.title2)
Text("Toggles a smaller, unified view with hidden app list")
.font(.footnote)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $mini, label: {
})
.toggleStyle(.switch)
.onChange(of: mini) { newVal in
resizeWindow()
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color("mode").opacity(0.05))
)
Spacer() Spacer()
} }
} }
.padding(20) .padding(20)
.frame(width: 400, height: 100) .frame(width: 400, height: 250)
} }
+6 -1
View File
@@ -124,6 +124,11 @@ func checkForUpdate(appState: AppState, manual: Bool = false) {
} }
} }
func launchUpdate() {
NSWorkspace.shared.open(URL(string: "https://github.com/alienator88/Pearcleaner/releases")!)
}
func downloadUpdate(appState: AppState) { func downloadUpdate(appState: AppState) {
guard let latestRelease = appState.releases.first else { return } guard let latestRelease = appState.releases.first else { return }
guard let asset = latestRelease.assets.first else { return } guard let asset = latestRelease.assets.first else { return }
@@ -140,7 +145,7 @@ func downloadUpdate(appState: AppState) {
do { do {
if fileManager.fileExists(atPath: destinationURL.path) { if fileManager.fileExists(atPath: destinationURL.path) {
try fileManager.removeItem(at: destinationURL) try? fileManager.removeItem(at: destinationURL)
} }
try fileManager.moveItem(at: localURL, to: destinationURL) try fileManager.moveItem(at: localURL, to: destinationURL)
+32 -15
View File
@@ -53,7 +53,24 @@ struct AppListView: View {
VStack(alignment: .center) { VStack(alignment: .center) {
VStack(alignment: .center, spacing: 20) { VStack(alignment: .center, spacing: 20) {
SearchBar(search: $search) HStack {
SearchBar(search: $search)
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
// 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()
}
}
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh app list", color: Color("mode")))
}
} }
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 20) .padding(.top, 20)
@@ -157,20 +174,20 @@ struct AppDetailsEmptyView: View {
Spacer() Spacer()
if appState.isReminderVisible { // if appState.isReminderVisible {
Text("􀆔 + Z to undo") // Text("􀆔 + Z to undo")
.font(.title2) // .font(.title2)
.foregroundStyle(Color("AccentColor").opacity(0.5)) // .foregroundStyle(Color("AccentColor").opacity(0.5))
.fontWeight(.medium) // .fontWeight(.medium)
.onAppear { // .onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { // DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation { // withAnimation {
appState.isReminderVisible = false // appState.isReminderVisible = false
} // }
} // }
} // }
Spacer() // Spacer()
} // }
Text("Drop an app above or select one from the list to begin") Text("Drop an app above or select one from the list to begin")
.font(.title3) .font(.title3)
+8 -4
View File
@@ -11,6 +11,7 @@ import UniformTypeIdentifiers
struct DropTarget: View, DropDelegate { struct DropTarget: View, DropDelegate {
@AppStorage("settings.general.ants") private var ants: Bool = false @AppStorage("settings.general.ants") private var ants: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false
@ObservedObject var appState: AppState @ObservedObject var appState: AppState
@State private var shouldFlash = false @State private var shouldFlash = false
private var types: [UTType] = [.fileURL] private var types: [UTType] = [.fileURL]
@@ -34,15 +35,16 @@ struct DropTarget: View, DropDelegate {
ZStack { ZStack {
LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing) LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing)
.mask(Image("logo") .mask(
Image(mini ? "pear" : "logo")
.resizable() .resizable()
.scaledToFit()) .scaledToFit()
.frame(width: mini ? 104 : 500)
)
} }
.frame(width: 500, height: 200)
Spacer() Spacer()
} }
Spacer() Spacer()
@@ -71,6 +73,8 @@ struct DropTarget: View, DropDelegate {
} }
.onDrop(of: types, delegate: self) .onDrop(of: types, delegate: self)
.padding(20) .padding(20)
.frame(maxWidth: mini ? 200 : .infinity)
.frame(maxHeight: mini ? 200 : .infinity)
} }
+9 -7
View File
@@ -14,6 +14,7 @@ struct FilesView: View {
@State private var showDetails: Bool = false @State private var showDetails: Bool = false
@State private var showPop: Bool = false @State private var showPop: Bool = false
@State private var itemDetails: [(size: String, icon: Image?)] = [] @State private var itemDetails: [(size: String, icon: Image?)] = []
@AppStorage("settings.general.mini") private var mini: Bool = false
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
var body: some View { var body: some View {
@@ -34,8 +35,9 @@ struct FilesView: View {
if let appIcon = appState.appInfo.appIcon { if let appIcon = appState.appInfo.appIcon {
Image(nsImage: appIcon) Image(nsImage: appIcon)
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .scaledToFit()
.frame(width: 100, height: 100) // .aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
.padding(.leading) .padding(.leading)
} }
//app title, size and items //app title, size and items
@@ -43,19 +45,19 @@ struct FilesView: View {
HStack(alignment: .center) { HStack(alignment: .center) {
VStack(alignment: .leading, spacing: 5){ VStack(alignment: .leading, spacing: 5){
HStack { HStack {
Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold) Text("\(appState.appInfo.appName)").font(mini ? .title2 :.title).fontWeight(.bold)
// .foregroundStyle(Color("AccentColor")) // .foregroundStyle(Color("AccentColor"))
Text("").foregroundStyle(Color("AccentColor")) Text("").foregroundStyle(Color("AccentColor"))
Text("\(appState.appInfo.appVersion)").font(.title3) Text("\(appState.appInfo.appVersion)").font(.title3)
// .foregroundStyle(.gray.opacity(0.8)) // .foregroundStyle(.gray.opacity(0.8))
} }
Text("\(appState.appInfo.bundleIdentifier)").font(.title3) Text("\(appState.appInfo.bundleIdentifier)").font(mini ? .footnote :.title3)
.foregroundStyle((.gray.opacity(0.8))) .foregroundStyle((.gray.opacity(0.8)))
} }
Spacer() Spacer()
Text("\(appSize)").font(.title).fontWeight(.bold) Text("\(appSize)").font(mini ? .title2 :.title).fontWeight(.bold)
// .foregroundStyle(Color("AccentColor")) // .foregroundStyle(Color("AccentColor"))
} }
@@ -180,7 +182,7 @@ struct FilesView: View {
} }
} }
.frame(minWidth: 700) // .frame(minWidth: 700)
.onAppear { .onAppear {
Task { Task {
calculateFileDetails() calculateFileDetails()
@@ -291,7 +293,7 @@ struct FileDetailsItem: View {
Button("") { Button("") {
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path) NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)
} }
.buttonStyle(SimpleButtonStyle(icon: "folder.fill", help: "Show in Finder", color: Color("AccentColor"))) .buttonStyle(SimpleButtonStyle(icon: "folder.fill", help: "Show in Finder", color: Color("mode")))
} }
.background( .background(
+203
View File
@@ -0,0 +1,203 @@
//
// MiniMode.swift
// Pearcleaner
//
// Created by Alin Lupascu on 11/14/23.
//
import Foundation
import SwiftUI
struct MiniMode: View {
@EnvironmentObject var appState: AppState
@Binding var search: String
@State private var showSys: Bool = true
@State private var showUsr: Bool = true
@State private var reload: Bool = false
var body: some View {
HStack(alignment: .center, spacing: 0) {
// Main Mini View
VStack(spacing: 0) {
if appState.currentView == .empty {
TopBarMini(reload: $reload, search: $search)
AppDetailsEmptyView()
} else if appState.currentView == .files {
TopBarMini(reload: $reload, search: $search)
FilesView()
.id(appState.appInfo.id)
} else if appState.currentView == .apps {
TopBarMini(reload: $reload, search: $search)
MiniAppView(search: $search, reload: $reload)
}
}
// .padding(.leading, appState.sidebar ? 0 : 10)
.transition(.move(edge: .leading))
}
.edgesIgnoringSafeArea(.all)
.onOpenURL(perform: { url in
let deeplinkManager = DeeplinkManager()
deeplinkManager.manage(url: url, appState: appState)
})
// MARK: Background for whole app
// .background(Color("bg").opacity(1))
// .background(VisualEffect(material: .sidebar, blendingMode: .behindWindow).edgesIgnoringSafeArea(.all))
}
}
struct MiniEmptyView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var appState: AppState
@State private var animateGradient: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false
var body: some View {
VStack() {
Spacer()
DropTarget(appState: appState)
.frame(maxWidth: mini ? 300 : 500)
Spacer()
if appState.isReminderVisible {
Text("CMD + Z to undo")
.font(.title2)
.foregroundStyle(Color("AccentColor").opacity(0.5))
.fontWeight(.medium)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
appState.isReminderVisible = false
}
}
}
Spacer()
}
Text("Drop an app above or select one from the list to begin")
.font(.title3)
.padding(.bottom, 25)
.opacity(0.5)
}
}
}
struct MiniAppView: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var appState: AppState
@State private var animateGradient: Bool = false
@Binding var search: String
@State private var showSys: Bool = true
@State private var showUsr: Bool = true
@Binding var reload: Bool
@AppStorage("settings.general.mini") private var mini: Bool = false
var body: some View {
var filteredUserApps: [AppInfo] {
if search.isEmpty {
return appState.sortedApps.userApps
} else {
return appState.sortedApps.userApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
}
}
var filteredSystemApps: [AppInfo] {
if search.isEmpty {
return appState.sortedApps.systemApps
} else {
return appState.sortedApps.systemApps.filter { $0.appName.localizedCaseInsensitiveContains(search) }
}
}
ZStack {
HStack(spacing: 0){
if reload {
VStack {
Spacer()
ProgressView("Refreshing applications")
Spacer()
}
.padding(.vertical)
} else {
VStack(alignment: .center) {
// VStack(alignment: .center, spacing: 20) {
// HStack {
// SearchBar(search: $search)
// }
// }
// .padding(.horizontal, 5)
// .padding(.bottom)
ScrollView {
VStack(alignment: .leading) {
if filteredUserApps.count > 0 {
VStack {
Header(title: "User", count: filteredUserApps.count)
ForEach(filteredUserApps, id: \.self) { appInfo in
AppListItems(appInfo: appInfo)
if appInfo != filteredUserApps.last {
Divider().padding(.horizontal, 5)
}
}
.padding(.bottom)
}
}
if filteredSystemApps.count > 0 {
VStack {
Header(title: "System", count: filteredSystemApps.count)
ForEach(filteredSystemApps, id: \.self) { appInfo in
AppListItems(appInfo: appInfo)
if appInfo != filteredSystemApps.last {
Divider().padding(.horizontal, 5)
}
}
}
}
}
.padding(.horizontal)
}
.scrollIndicators(.never)
}
.padding(.vertical)
}
}
}
.transition(.move(edge: .leading))
}
}
+23 -71
View File
@@ -9,57 +9,19 @@ import Foundation
import SwiftUI import SwiftUI
struct TopBar: View { struct TopBar: View {
// @Binding var sidebar: Bool // @Binding var sidebar: Bool
@Binding var reload: Bool @Binding var reload: Bool
@AppStorage("displayMode") var displayMode: DisplayMode = .system @AppStorage("displayMode") var displayMode: DisplayMode = .system
@AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.glass") private var glass: Bool = false
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false @AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
var body: some View { var body: some View {
HStack(alignment: .center, spacing: 20) { HStack(alignment: .center, spacing: 20) {
HStack(alignment: .center, spacing: 5) {
// Button("") {
// withAnimation(.easeInOut(duration: 0.5)) {
// appState.sidebar.toggle()
// if appState.sidebar {
// appState.winWidth += 300
// } else {
// appState.winWidth -= 300
// }
// }
// }
// .buttonStyle(SimpleButtonStyle(icon: "sidebar.left", help: "Toggle Sidebar", color: Color("AccentColor")))
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
// 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()
}
}
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.circlepath", help: "Refresh app list", color: Color("AccentColor")))
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
openTrash()
}
}
.buttonStyle(SimpleButtonStyle(icon: "trash", help: "Open Trash", color: Color("AccentColor")))
}
Spacer() Spacer()
if appState.currentView != .empty { if appState.currentView != .empty {
Button("") { Button("") {
withAnimation(.easeInOut(duration: 0.5)) { withAnimation(.easeInOut(duration: 0.5)) {
@@ -67,48 +29,38 @@ struct TopBar: View {
appState.appInfo = AppInfo.empty appState.appInfo = AppInfo.empty
} }
} }
.buttonStyle(SimpleButtonStyle(icon: "house", help: "Home", color: Color("AccentColor"))) .buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode")))
} }
if appState.isReminderVisible {
Text("CMD + Z to undo")
.font(.title2)
.foregroundStyle(Color("AccentColor").opacity(0.5))
.fontWeight(.medium)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
appState.isReminderVisible = false
}
}
}
}
Spacer() Spacer()
HStack(alignment: .center, spacing: 5) {
if sentinel {
Button("") {
//
}
.buttonStyle(SimpleButtonStyle(icon: "lock.shield", help: "Sentinel enabled", color: .green, shield: true))
}
Button("") { // if sentinel {
withAnimation(.easeInOut(duration: 0.5)) { // Button("") {
glass.toggle() // //
} // }
} // .buttonStyle(SimpleButtonStyle(icon: "lock.shield", help: "Sentinel enabled", color: .green, shield: true))
.buttonStyle(SimpleButtonStyle(icon: glass ? "circle.dashed" : "circle.dashed.inset.filled", help: "Toggle transparency", color: Color("AccentColor"))) // }
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
if displayMode.colorScheme == .dark {
displayMode.colorScheme = .light
} else if displayMode.colorScheme == .light {
displayMode.colorScheme = .dark
} else {
displayMode.colorScheme = .dark
}
}
}
.buttonStyle(SimpleButtonStyle(icon: displayMode.colorScheme == .dark ? "sun.max.fill" : "moon.fill", help: "Toggle appearance", color: Color("AccentColor")))
}
} }
.padding(.horizontal, 20) .padding(.horizontal, 20)
.padding(.top, 30) .padding(.top, 20)
} }
} }
+111
View File
@@ -0,0 +1,111 @@
//
// TopBarMini.swift
// Pearcleaner
//
// Created by Alin Lupascu on 11/14/23.
//
import Foundation
import SwiftUI
struct TopBarMini: View {
@Binding var reload: Bool
@AppStorage("displayMode") var displayMode: DisplayMode = .system
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false
@Binding var search: String
@EnvironmentObject var appState: AppState
var body: some View {
HStack(alignment: .center, spacing: 5) {
Spacer()
if appState.currentView == .apps {
HStack(alignment: .center, spacing: 0) {
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
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)) {
appState.currentView = .apps
}
}
.buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps", color: Color("mode")))
} else if appState.currentView == .files {
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
appState.currentView = .empty
appState.appInfo = AppInfo.empty
}
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.down.app", help: "Drop", color: Color("mode")))
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
appState.currentView = .apps
}
}
.buttonStyle(SimpleButtonStyle(icon: "list.triangle", help: "Apps", color: Color("mode")))
}
if appState.currentView == .apps {
HStack {
Spacer()
SearchBarMini(search: $search)
Spacer()
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
// 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()
}
}
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.triangle.2.circlepath", help: "Refresh app list", color: Color("mode")))
}
}
Spacer()
// if sentinel {
// Button("") {
// //
// }
// .buttonStyle(SimpleButtonStyle(icon: "lock.shield", help: "Sentinel enabled", color: .green, shield: true))
// }
}
.edgesIgnoringSafeArea(.all)
.padding(.horizontal)
.padding(.top, 30)
}
}
struct SearchBarMini: View {
@Binding var search: String
var body: some View {
HStack {
TextField("Search", text: $search)
.textFieldStyle(SimpleSearchStyle(trash: true, text: $search))
}
.frame(height: 30)
}
}