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 = {
/* 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 */; };
C72893122AFD51EA00C8C1CD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C72893112AFD51EA00C8C1CD /* main.swift */; };
C7575D382B0181FC006A600A /* FileWatcher in Frameworks */ = {isa = PBXBuildFile; productRef = C7575D372B0181FC006A600A /* FileWatcher */; };
@@ -59,6 +61,8 @@
/* End PBXCopyFilesBuildPhase 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>"; };
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>"; };
@@ -206,9 +210,11 @@
C77B90222AF2D616009CC655 /* FilesView.swift */,
C76D08542AF89CDE00D07867 /* AppListView.swift */,
C7D31D472AFEB23700C7ED9E /* TopBar.swift */,
C7045A292B03FAF000376976 /* TopBarMini.swift */,
C7D31D492AFEB26700C7ED9E /* AppListItems.swift */,
C7D31D522B0048BE00C7ED9E /* TrashView.swift */,
C7D31D582B00A49300C7ED9E /* DropTarget.swift */,
C7045A272B03E71D00376976 /* MiniMode.swift */,
);
path = Views;
sourceTree = "<group>";
@@ -336,7 +342,9 @@
C7D31D462AFDA90C00C7ED9E /* Sentinel.swift in Sources */,
C7D31D592B00A49300C7ED9E /* DropTarget.swift in Sources */,
C77B90062AF18E2E009CC655 /* ContentView.swift in Sources */,
C7045A2A2B03FAF000376976 /* TopBarMini.swift in Sources */,
C77B901C2AF193A3009CC655 /* Styles.swift in Sources */,
C7045A282B03E71D00376976 /* MiniMode.swift in Sources */,
C72893052AFD42E600C8C1CD /* DeepLink.swift in Sources */,
C76D084C2AF83C9D00D07867 /* Permissions.swift in Sources */,
C76D084A2AF83C6E00D07867 /* General.swift in Sources */,
@@ -517,7 +525,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES;
@@ -534,7 +542,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.1;
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -551,7 +559,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BK8443AXLU;
ENABLE_HARDENED_RUNTIME = YES;
@@ -568,7 +576,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.1;
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.alienator88.Pearcleaner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
+8
View File
@@ -19,6 +19,7 @@ class AppState: ObservableObject
@Published var selectedItems = Set<URL>()
@Published var alertType = AlertType.off
@Published var currentView = CurrentDetailsView.empty
// @Published var currentMode = CurrentMode.regular
@Published var showAlert: Bool = false
@Published var sidebar: Bool = true
@Published var isReminderVisible: Bool = false
@@ -74,8 +75,15 @@ enum CurrentDetailsView:Int
{
case empty
case files
case apps
}
//enum CurrentMode:Int
//{
// case regular
// case mini
//}
enum AlertType:Int
{
case diskAccess
+50 -17
View File
@@ -17,25 +17,58 @@ class DeeplinkManager {
}
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
if url.pathExtension == "app" {
handleAppBundle(url: url, appState: appState)
} else if url.scheme == DeepLinkConstants.scheme,
url.host == DeepLinkConstants.host,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let queryItems = components.queryItems {
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")
}
} 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()
.scaledToFit()
.frame(width: 20)
.foregroundColor(color)
.foregroundColor(hovered ? color : color.opacity(0.5))
}
.padding(8)
.background {
if hovered && !(shield ?? false) {
// Circle()
// .background {
// if hovered && !(shield ?? false) {
//// Circle()
//// .strokeBorder(Color("AccentColor"), lineWidth: 1)
// RoundedRectangle(cornerRadius: 8)
// .strokeBorder(Color("AccentColor"), lineWidth: 1)
RoundedRectangle(cornerRadius: 8)
.strokeBorder(Color("AccentColor"), lineWidth: 1)
}
}
// }
//
// }
.onHover { hovering in
withAnimation() {
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
func checkAndRequestFullDiskAccess(appState: AppState, skipAlert: Bool = false) -> Bool {
@AppStorage("settings.permissions.disk") var diskP: Bool = false
@@ -268,8 +277,9 @@ func presentAlert(appState: AppState) -> Alert {
switch appState.alertType {
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")) {
downloadUpdate(appState: appState)
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)
launchUpdate()
appState.alertType = .off
}, secondaryButton: .cancel())
case .no_update:
@@ -278,7 +288,7 @@ func presentAlert(appState: AppState) -> Alert {
appState.alertType = .off
})
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") {
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.events") private var diskE: Bool = false
@AppStorage("displayMode") var displayMode: DisplayMode = .dark
@AppStorage("settings.general.mini") private var mini: Bool = false
@State private var search = ""
var body: some Scene {
WindowGroup {
AppListView(search: $search)
.environmentObject(appState)
.frame(minWidth: 1020, minHeight: 700)
.preferredColorScheme(displayMode.colorScheme)
.alert(isPresented: $appState.showAlert) { presentAlert(appState: appState) }
.handlesExternalEvents(preferring: Set(arrayLiteral: "pear"), allowing: Set(arrayLiteral: "*"))
.onAppear {
NSWindow.allowsAutomaticWindowTabbing = false
if !mini {
AppListView(search: $search)
.environmentObject(appState)
.frame(minWidth: 1020, minHeight: 700)
.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
// Get Apps
let sortedApps = getSortedApps()
appState.sortedApps.userApps = sortedApps.userApps
appState.sortedApps.systemApps = sortedApps.systemApps
Task {
Task {
#if !DEBUG
// Make sure App Support folder exists in the future if needed for storage
ensureApplicationSupportFolderExists(appState: appState)
// 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 {
// 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
}
}
}
} 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)
.windowToolbarStyle(.unified)
@@ -73,6 +127,7 @@ struct PearcleanerApp: App {
CommandGroup(replacing: .newItem, addition: { })
}
Settings {
SettingsView()
.environmentObject(appState)
@@ -89,4 +144,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
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" : [
{
"filename" : "logo_combo.png",
"filename" : "logo_combo_small.png",
"idiom" : "universal",
"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">
<plist version="1.0">
<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>
<array>
<dict>
+1 -1
View File
@@ -29,7 +29,7 @@ struct AboutSettingsTab: View {
.buttonStyle(SimpleButtonStyle(icon: "cloud", help: "Update application", color: Color("AccentColor")))
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")))
}
+45 -19
View File
@@ -11,7 +11,9 @@ import SwiftUI
struct GeneralSettingsTab: View {
@EnvironmentObject var appState: AppState
@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 {
Form {
@@ -34,30 +36,54 @@ struct GeneralSettingsTab: View {
.fill(Color("mode").opacity(0.05))
)
// HStack {
// VStack(alignment: .leading, spacing: 5) {
// Text("Animation").font(.title2)
// Text("Toggles the marching ants animation on the drop zone")
// .font(.footnote)
// .foregroundStyle(.gray)
// }
// Spacer()
// Toggle(isOn: $ants, label: {
// })
// .toggleStyle(.switch)
// }
// .padding()
// .background(
// RoundedRectangle(cornerRadius: 8)
// .fill(Color("mode").opacity(0.05))
// )
HStack {
VStack(alignment: .leading, spacing: 5) {
Text("Dark Mode").font(.title2)
Text("Toggles between dark and light mode")
.font(.footnote)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $isDark, label: {
})
.toggleStyle(.switch)
.onChange(of: isDark) { newValue in
displayMode.colorScheme = newValue ? .dark : .light
}
}
.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()
}
}
.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) {
guard let latestRelease = appState.releases.first else { return }
guard let asset = latestRelease.assets.first else { return }
@@ -140,7 +145,7 @@ func downloadUpdate(appState: AppState) {
do {
if fileManager.fileExists(atPath: destinationURL.path) {
try fileManager.removeItem(at: destinationURL)
try? fileManager.removeItem(at: 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, 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(.top, 20)
@@ -157,20 +174,20 @@ struct AppDetailsEmptyView: View {
Spacer()
if appState.isReminderVisible {
Text("􀆔 + 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()
}
// if appState.isReminderVisible {
// Text("􀆔 + 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)
+8 -4
View File
@@ -11,6 +11,7 @@ import UniformTypeIdentifiers
struct DropTarget: View, DropDelegate {
@AppStorage("settings.general.ants") private var ants: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false
@ObservedObject var appState: AppState
@State private var shouldFlash = false
private var types: [UTType] = [.fileURL]
@@ -34,15 +35,16 @@ struct DropTarget: View, DropDelegate {
ZStack {
LinearGradient(gradient: Gradient(colors: [.pink, .orange]), startPoint: .leading, endPoint: .trailing)
.mask(Image("logo")
.mask(
Image(mini ? "pear" : "logo")
.resizable()
.scaledToFit())
.scaledToFit()
.frame(width: mini ? 104 : 500)
)
}
.frame(width: 500, height: 200)
Spacer()
}
Spacer()
@@ -71,6 +73,8 @@ struct DropTarget: View, DropDelegate {
}
.onDrop(of: types, delegate: self)
.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 showPop: Bool = false
@State private var itemDetails: [(size: String, icon: Image?)] = []
@AppStorage("settings.general.mini") private var mini: Bool = false
@Environment(\.colorScheme) var colorScheme
var body: some View {
@@ -34,8 +35,9 @@ struct FilesView: View {
if let appIcon = appState.appInfo.appIcon {
Image(nsImage: appIcon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100)
.scaledToFit()
// .aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
.padding(.leading)
}
//app title, size and items
@@ -43,19 +45,19 @@ struct FilesView: View {
HStack(alignment: .center) {
VStack(alignment: .leading, spacing: 5){
HStack {
Text("\(appState.appInfo.appName)").font(.title).fontWeight(.bold)
Text("\(appState.appInfo.appName)").font(mini ? .title2 :.title).fontWeight(.bold)
// .foregroundStyle(Color("AccentColor"))
Text("").foregroundStyle(Color("AccentColor"))
Text("\(appState.appInfo.appVersion)").font(.title3)
// .foregroundStyle(.gray.opacity(0.8))
}
Text("\(appState.appInfo.bundleIdentifier)").font(.title3)
Text("\(appState.appInfo.bundleIdentifier)").font(mini ? .footnote :.title3)
.foregroundStyle((.gray.opacity(0.8)))
}
Spacer()
Text("\(appSize)").font(.title).fontWeight(.bold)
Text("\(appSize)").font(mini ? .title2 :.title).fontWeight(.bold)
// .foregroundStyle(Color("AccentColor"))
}
@@ -180,7 +182,7 @@ struct FilesView: View {
}
}
.frame(minWidth: 700)
// .frame(minWidth: 700)
.onAppear {
Task {
calculateFileDetails()
@@ -291,7 +293,7 @@ struct FileDetailsItem: View {
Button("") {
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(
+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
struct TopBar: View {
// @Binding var sidebar: Bool
// @Binding var sidebar: Bool
@Binding var reload: Bool
@AppStorage("displayMode") var displayMode: DisplayMode = .system
@AppStorage("settings.general.glass") private var glass: Bool = false
@AppStorage("settings.sentinel.enable") private var sentinel: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false
@EnvironmentObject var appState: AppState
var body: some View {
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()
if appState.currentView != .empty {
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
@@ -67,48 +29,38 @@ struct TopBar: View {
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()
HStack(alignment: .center, spacing: 5) {
if sentinel {
Button("") {
//
}
.buttonStyle(SimpleButtonStyle(icon: "lock.shield", help: "Sentinel enabled", color: .green, shield: true))
}
Button("") {
withAnimation(.easeInOut(duration: 0.5)) {
glass.toggle()
}
}
.buttonStyle(SimpleButtonStyle(icon: glass ? "circle.dashed" : "circle.dashed.inset.filled", help: "Toggle transparency", color: Color("AccentColor")))
// if sentinel {
// Button("") {
// //
// }
// .buttonStyle(SimpleButtonStyle(icon: "lock.shield", help: "Sentinel enabled", color: .green, shield: true))
// }
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(.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)
}
}