mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 04:58:56 +00:00
v1.2
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,15 +17,12 @@ class DeeplinkManager {
|
||||
}
|
||||
|
||||
func manage(url: URL, appState: AppState) {
|
||||
guard url.scheme == DeepLinkConstants.scheme,
|
||||
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
|
||||
else {
|
||||
print("URL does not match the expected scheme and host")
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -37,5 +34,41 @@ class DeeplinkManager {
|
||||
} else {
|
||||
print("No path query parameter found in the URL")
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@ 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 {
|
||||
if !mini {
|
||||
AppListView(search: $search)
|
||||
.environmentObject(appState)
|
||||
.frame(minWidth: 1020, minHeight: 700)
|
||||
@@ -41,6 +43,56 @@ struct PearcleanerApp: App {
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
@@ -65,6 +117,8 @@ struct PearcleanerApp: App {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
.windowToolbarStyle(.unified)
|
||||
.windowResizability(.contentMinSize)
|
||||
@@ -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 |
@@ -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>
|
||||
|
||||
@@ -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")))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -53,7 +53,24 @@ struct AppListView: View {
|
||||
VStack(alignment: .center) {
|
||||
|
||||
VStack(alignment: .center, spacing: 20) {
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user