Road to v4.0.0

This commit is contained in:
Alin
2024-09-27 20:11:44 -06:00
parent c248e4c8ce
commit abdf1cfc3a
17 changed files with 1295 additions and 1024 deletions
+6
View File
@@ -14,6 +14,7 @@
C74412C72BBF249000DDFCA8 /* AppPathsFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */; };
C74FDC632BCD833D00B8960F /* Conditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C74FDC622BCD833D00B8960F /* Conditions.swift */; };
C7575D392B0182DE006A600A /* PearcleanerSentinel in CopyFiles */ = {isa = PBXBuildFile; fileRef = C728930F2AFD51EA00C8C1CD /* PearcleanerSentinel */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
C764680F2CA7790200DC72DF /* PearGroupBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = C764680E2CA778FB00DC72DF /* PearGroupBox.swift */; };
C76D08482AF83C3F00D07867 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08472AF83C3F00D07867 /* Update.swift */; };
C76D084A2AF83C6E00D07867 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08492AF83C6E00D07867 /* General.swift */; };
C76D08512AF850F300D07867 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = C76D08502AF850F300D07867 /* About.swift */; };
@@ -99,6 +100,7 @@
C72893112AFD51EA00C8C1CD /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
C74412C62BBF249000DDFCA8 /* AppPathsFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPathsFetch.swift; sourceTree = "<group>"; };
C74FDC622BCD833D00B8960F /* Conditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conditions.swift; sourceTree = "<group>"; };
C764680E2CA778FB00DC72DF /* PearGroupBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PearGroupBox.swift; sourceTree = "<group>"; };
C76D08472AF83C3F00D07867 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = "<group>"; };
C76D08492AF83C6E00D07867 /* General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = "<group>"; };
C76D08502AF850F300D07867 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = "<group>"; };
@@ -177,6 +179,7 @@
C7F539372AF60865007DF1B2 /* Utilities.swift */,
C72893042AFD42E600C8C1CD /* DeepLink.swift */,
C77B901B2AF193A3009CC655 /* Styles.swift */,
C764680E2CA778FB00DC72DF /* PearGroupBox.swift */,
C7D31D502AFF00F300C7ED9E /* Locations.swift */,
C74FDC622BCD833D00B8960F /* Conditions.swift */,
C7DE672A2BA6343D00EB1633 /* MenuBarItem.swift */,
@@ -429,6 +432,7 @@
buildActionMask = 2147483647;
files = (
C7DD49EA2BAB4D8100CCBA16 /* AppInfoFetch.swift in Sources */,
C764680F2CA7790200DC72DF /* PearGroupBox.swift in Sources */,
C79947962C34A794007279CC /* Tips.swift in Sources */,
C76D08482AF83C3F00D07867 /* Update.swift in Sources */,
C7FB173B2B96321300B96F9A /* AppsListView.swift in Sources */,
@@ -683,6 +687,7 @@
INFOPLIST_KEY_NSAppleEventsUsageDescription = "Pearcleaner needs to send apple events to FInder to delete files";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSSystemAdministrationUsageDescription = "Pearcleaner requires full disk access to be able to delete files in locations with no permission";
INFOPLIST_KEY_NSSystemExtensionUsageDescription = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
@@ -722,6 +727,7 @@
INFOPLIST_KEY_NSAppleEventsUsageDescription = "Pearcleaner needs to send apple events to FInder to delete files";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSSystemAdministrationUsageDescription = "Pearcleaner requires full disk access to be able to delete files in locations with no permission";
INFOPLIST_KEY_NSSystemExtensionUsageDescription = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
@@ -7,7 +7,7 @@
"location" : "https://github.com/alienator88/AlinFoundation",
"state" : {
"branch" : "main",
"revision" : "eb160d04a32e1e2fe54bfa3fac1f6a7535d7b2fb"
"revision" : "579f5dd4730f085ad0163442a6c81d1c5fb65e90"
}
},
{
+1 -1
View File
@@ -31,7 +31,7 @@ struct AppCommands: Commands {
CommandGroup(replacing: .appInfo) {
Button {
updater.checkForUpdates()
updater.checkForUpdatesForce(showSheet: true)
} label: {
Text("Check for Updates")
}
+136
View File
@@ -0,0 +1,136 @@
//
// IceGroupBox.swift
// Pearcleaner
//
// Created by Jordan Baird (Ice), modified for Pearcleaner usage by Alin Lupascu on 9/27/24.
//
import SwiftUI
struct PearGroupBox<Header: View, Content: View, Footer: View>: View {
private let header: Header
private let content: Content
private let footer: Footer
private let padding: CGFloat
/// Example usage:
/// ```
/// PearGroupBox(
/// header: { Text("Header View") },
/// content: { Text("Content View") },
/// footer: { Text("Footer View") }
/// )
/// ```
init(
padding: CGFloat = 10,
@ViewBuilder header: () -> Header,
@ViewBuilder content: () -> Content,
@ViewBuilder footer: () -> Footer
) {
self.padding = padding
self.header = header()
self.content = content()
self.footer = footer()
}
/// Example usage with no header:
/// ```
/// PearGroupBox(
/// content: { Text("Content View") },
/// footer: { Text("Footer View") }
/// )
/// ```
init(
padding: CGFloat = 10,
@ViewBuilder content: () -> Content,
@ViewBuilder footer: () -> Footer
) where Header == EmptyView {
self.init(padding: padding) {
EmptyView()
} content: {
content()
} footer: {
footer()
}
}
/// Example usage with no footer:
/// ```
/// PearGroupBox(
/// header: { Text("Header View") },
/// content: { Text("Content View") }
/// )
/// ```
init(
padding: CGFloat = 10,
@ViewBuilder header: () -> Header,
@ViewBuilder content: () -> Content
) where Footer == EmptyView {
self.init(padding: padding) {
header()
} content: {
content()
} footer: {
EmptyView()
}
}
/// Example usage with content only:
/// ```
/// PearGroupBox {
/// Text("Content View Only")
/// }
/// ```
init(
padding: CGFloat = 10,
@ViewBuilder content: () -> Content
) where Header == EmptyView, Footer == EmptyView {
self.init(padding: padding) {
EmptyView()
} content: {
content()
} footer: {
EmptyView()
}
}
/// Example usage with a title and content only:
/// ```
/// PearGroupBox("Header Title") {
/// Text("Content View")
/// }
/// ```
init(
_ title: LocalizedStringKey,
padding: CGFloat = 10,
@ViewBuilder content: () -> Content
) where Header == Text, Footer == EmptyView {
self.init(padding: padding) {
Text(title)
.font(.headline)
} content: {
content()
}
}
var body: some View {
VStack(alignment: .leading) {
header
content
.padding(padding)
.background {
backgroundShape
.fill(.quinary)
.overlay {
backgroundShape
.stroke(.quaternary)
}
}
footer
}
}
@ViewBuilder
private var backgroundShape: some Shape {
RoundedRectangle(cornerRadius: 7, style: .circular)
}
}
+28 -6
View File
@@ -282,12 +282,37 @@ struct SimpleButtonBrightStyle: ButtonStyle {
struct PearDropView: View {
var multiplier: CGFloat = 1.0 // Multiplier parameter with a default value
var body: some View {
VStack(alignment: .center, spacing: 0) {
HStack(spacing: 0) {
LinearGradient(gradient: Gradient(colors: [.green, .orange]), startPoint: .leading, endPoint: .trailing)
.frame(width: 220)
.frame(width: 220 * multiplier) // Adjust the width based on the multiplier
LinearGradient(gradient: Gradient(colors: [.orange, .primary.opacity(0.5)]), startPoint: .leading, endPoint: .trailing)
.frame(width: 10 * multiplier) // Adjust the width based on the multiplier
LinearGradient(gradient: Gradient(colors: [.primary.opacity(0.5), .primary.opacity(0.5)]), startPoint: .leading, endPoint: .trailing)
.frame(width: 300 * multiplier) // Adjust the width based on the multiplier
}
.mask(
Image("logo_text_small")
.resizable()
.scaledToFit()
.frame(width: 500 * multiplier) // Scale the mask image width
.padding()
)
}
.frame(height: 120 * multiplier) // Adjust the height of the VStack based on the multiplier
}
}
struct PearDropViewSmall: View {
var body: some View {
VStack(alignment: .center, spacing: 0) {
HStack(spacing: 0) {
LinearGradient(gradient: Gradient(colors: [.green, .orange]), startPoint: .leading, endPoint: .trailing)
.frame(width: 260)
LinearGradient(gradient: Gradient(colors: [.orange, .primary.opacity(0.5)]), startPoint: .leading, endPoint: .trailing)
.frame(width: 10)
LinearGradient(gradient: Gradient(colors: [.primary.opacity(0.5), .primary.opacity(0.5)]), startPoint: .leading, endPoint: .trailing)
@@ -297,13 +322,10 @@ struct PearDropView: View {
Image("logo_text_small")
.resizable()
.scaledToFit()
.frame(width: 500)
.padding()
.frame(width: 180)
)
}
.frame(height: 120)
.frame(height: 50)
}
}
+39
View File
@@ -703,6 +703,45 @@ extension URL {
}
}
// Removes the sidebar toggle button from the toolbar, if running on macOS 14.0 or newer.
extension View {
@ViewBuilder
func removeSidebarToggle() -> some View {
if #available(macOS 14.0, *) {
self.toolbar(removing: .sidebarToggle)
} else {
self
}
}
}
// Drag window by background
extension View {
// Helper function to apply the movable background window
func movableByWindowBackground() -> some View {
self.background(MovableWindowAccessor())
}
}
// Custom NSWindow accessor to modify window properties
struct MovableWindowAccessor: NSViewRepresentable {
func makeNSView(context: Context) -> NSView {
let nsView = NSView()
DispatchQueue.main.async {
if let window = nsView.window {
// Enable dragging by the window's background
window.isMovableByWindowBackground = true
}
}
return nsView
}
func updateNSView(_ nsView: NSView, context: Context) {}
}
// Return image for different folders
func folderImages(for path: String) -> AnyView? {
if path.contains("/Library/Containers/") || path.contains("/Library/Group Containers/") {
+3 -1
View File
@@ -158,9 +158,11 @@ struct PearcleanerApp: App {
.environmentObject(themeManager)
.environmentObject(updater)
.environmentObject(permissionManager)
.toolbarBackground(.clear)
.preferredColorScheme(themeManager.displayMode.colorScheme)
.toolbarBackground(.clear)
.movableByWindowBackground()
}
}
}
-4
View File
@@ -2,10 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleShortVersionString</key>
<string>$(APP_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(APP_BUILD)</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
+24 -41
View File
@@ -19,12 +19,7 @@ struct AboutSettingsTab: View {
let credits = Credit.credits
VStack(alignment: .center) {
VStack(alignment: .center, spacing: 10) {
Image(nsImage: NSApp.applicationIconImage)
// .padding(.top, 20)
Text(Bundle.main.name)
.font(.title)
.bold()
@@ -33,7 +28,11 @@ struct AboutSettingsTab: View {
Text("(Build \(Bundle.main.buildVersion))").font(.footnote)
}
Text("Made with ❤️ by Alin Lupascu").font(.footnote)
VStack(spacing: 20) {
// GitHub
PearGroupBox(header: { Text("GitHub Issues").font(.title2) }, content: {
HStack{
Image(systemName: "star")
.resizable()
@@ -42,10 +41,9 @@ struct AboutSettingsTab: View {
.padding(.trailing)
VStack(alignment: .leading){
Text("GitHub")
Text("Submit a bug or feature request via the repo")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
Spacer()
@@ -55,14 +53,11 @@ struct AboutSettingsTab: View {
.buttonStyle(SimpleButtonStyle(icon: "link", help: "View"))
}
.padding(5)
.padding(.leading)
.padding(.top, 5)
}
Divider()
})
// GitHub Sponsors
PearGroupBox(header: { Text("GitHub Sponsors").font(.title2) }, content: {
HStack{
Image(systemName: "dollarsign.circle")
.resizable()
@@ -70,7 +65,9 @@ struct AboutSettingsTab: View {
.frame(width: 20, height: 20)
.padding(.trailing)
DisclosureGroup(isExpanded: $disclose, content: {
Text("View project contributors")
DisclosureGroup("", isExpanded: $disclose, content: {
ScrollView {
LazyVStack(alignment: .leading, spacing: 0) {
ForEach(sponsors) { sponsor in
@@ -81,6 +78,7 @@ struct AboutSettingsTab: View {
NSWorkspace.shared.open(sponsor.url)
}
.buttonStyle(SimpleButtonStyle(icon: "link", help: "View", padding: 5))
.padding(.trailing)
}
}
}
@@ -88,19 +86,12 @@ struct AboutSettingsTab: View {
}
.frame(height: 100)
.padding(5)
}, label: {
Text("GitHub Sponsors")
.font(.title2)
})
}
})
}
.padding(5)
.padding(.leading)
Divider()
// Credits
PearGroupBox(header: { Text("Credits").font(.title2) }, content: {
HStack{
Image(systemName: "info.circle")
.resizable()
@@ -108,7 +99,9 @@ struct AboutSettingsTab: View {
.frame(width: 20, height: 20)
.padding(.trailing)
DisclosureGroup(isExpanded: $discloseCredits, content: {
Text("View project resources")
DisclosureGroup("", isExpanded: $discloseCredits, content: {
VStack(alignment: .leading) {
ForEach(credits) { credit in
@@ -125,29 +118,20 @@ struct AboutSettingsTab: View {
NSWorkspace.shared.open(credit.url)
}
.buttonStyle(SimpleButtonStyle(icon: "link", help: "View"))
.padding(.trailing)
}
}
}
.padding(5)
}, label: {
Text("Credits")
.font(.title2)
})
}
})
}
}
.padding(5)
.padding(.leading)
Spacer()
Text("Made with ❤️ by Alin Lupascu").font(.footnote)//.padding(.bottom)
}
.padding(20)
.frame(width: 500, height: 500)
}
}
@@ -173,7 +157,6 @@ struct Sponsor: Identifiable {
//MARK: Credits
struct Credit: Identifiable {
let id = UUID()
// let icon: String
let name: String
let description: String
let url: URL
+28 -41
View File
@@ -22,24 +22,18 @@ struct FolderSettingsTab: View {
var body: some View {
Form {
VStack(spacing: 0) {
VStack(spacing: 20) {
HStack(spacing: 0) {
Text("Application Folders").font(.title2)
InfoButton(text: "Locations that will be searched for .app files. Click a non-default path to remove it. Add new folders using the + button or drag/drop over the list. Non-default paths can't be removed.")
// === Application Folders============================================================================================
PearGroupBox(header: {
HStack(alignment: .center, spacing: 0) {
Text("Search these folders for applications").font(.title2)
InfoButton(text: "Locations that will be searched for .app files. Click a non-default path to remove it. Default paths can't be removed.")
.padding(.leading, 5)
Spacer()
Button("") {
selectFolder()
}
.buttonStyle(SimpleButtonStyle(icon: "plus.circle", help: "Add folder", size: 16, rotate: true))
}
.padding(.bottom, 5)
}, content: {
VStack {
ScrollView {
VStack(spacing: 5) {
ForEach(fsm.folderPaths.indices, id: \.self) { index in
@@ -81,8 +75,6 @@ struct FolderSettingsTab: View {
.scrollIndicators(.automatic)
.padding()
.frame(height: 200)
// .background(.primary.opacity(0.05))
.background(backgroundView(themeManager: themeManager, darker: true, glass: glass))
.clipShape(RoundedRectangle(cornerRadius: 10))
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers -> Bool in
providers.forEach { provider in
@@ -103,31 +95,27 @@ struct FolderSettingsTab: View {
HStack {
Spacer()
Text("Drop folders here").opacity(0.5)
Text("Drop folders above or click to add").opacity(0.5)
Button("") {
selectFolder()
}
.buttonStyle(SimpleButtonStyle(icon: "plus.circle", help: "Add folder", size: 16, rotate: true))
Spacer()
}
.padding(.top, 5)
}
})
// === Leftover Folders============================================================================================
Divider()
.padding(.vertical)
// === LEFTOVER FILES ================================================================================================
PearGroupBox(header: {
HStack(spacing: 0) {
Text("Leftover Files Exclusions").font(.title2)
InfoButton(text: "Add files or folders that will be ignored when searching for leftover files. Click a path to remove it from the list. Add new files/folders using the + button or drag/drop over the list.")
Text("Exclude these files and folders from leftover search").font(.title2)
InfoButton(text: "Add files or folders that will be ignored when searching for leftover files. Click a path to remove it from the list.")
.padding(.leading, 5)
Spacer()
Button("") {
selectFilesFoldersZ()
}
.buttonStyle(SimpleButtonStyle(icon: "plus.circle", help: "Add file/folder", size: 16, rotate: true))
}
.padding(.bottom, 5)
}, content: {
VStack {
ScrollView {
VStack(spacing: 5) {
if fsm.fileFolderPathsZ.count == 0 {
@@ -175,8 +163,6 @@ struct FolderSettingsTab: View {
.scrollIndicators(.automatic)
.padding()
.frame(height: 200)
.background(backgroundView(themeManager: themeManager, darker: true, glass: glass))
// .background(.primary.opacity(0.05))
.clipShape(RoundedRectangle(cornerRadius: 10))
.onDrop(of: ["public.file-url"], isTargeted: nil) { providers -> Bool in
providers.forEach { provider in
@@ -196,17 +182,18 @@ struct FolderSettingsTab: View {
HStack {
Spacer()
Text("Drop files/folders here").opacity(0.5)
Text("Drop files or folders above or click to add").opacity(0.5)
Button("") {
selectFilesFoldersZ()
}
.buttonStyle(SimpleButtonStyle(icon: "plus.circle", help: "Add file/folder", size: 16, rotate: true))
Spacer()
}
.padding(.top, 5)
}
Spacer()
})
}
.padding(20)
.frame(width: 500, height: 580)
}
+64 -155
View File
@@ -21,51 +21,31 @@ struct GeneralSettingsTab: View {
@AppStorage("settings.general.confirmAlert") private var confirmAlert: Bool = false
@AppStorage("settings.general.selectedSort") var selectedSortAlpha: Bool = true
@AppStorage("settings.general.sizeType") var sizeType: String = "Real"
// @AppStorage("settings.general.brewTerminal") var brewTerm: String = "Terminal"
@State private var isResetting = false
@State private var isCLISymlinked = false
var body: some View {
VStack() {
HStack() {
Text("Functionality").font(.title2)
Spacer()
}
.padding(.leading)
VStack(spacing: 20) {
// === Functionality ================================================================================================
PearGroupBox(
header: { Text("Functionality").font(.title2) },
content: {
VStack {
HStack(spacing: 0) {
Image(systemName: brew ? "mug.fill" : "mug")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary.opacity(1))
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 0) {
Text("\(brew ? "Homebrew cleanup is enabled" : "Homebrew cleanup is disabled")")
Text("Homebrew cleanup after uninstall")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary.opacity(1))
InfoButton(text: "When homebrew cleanup is enabled, Pearcleaner will check if the app you are removing was installed via homebrew and launch Terminal.app to execute a brew uninstall and cleanup command to let homebrew know that the app is removed. This way your homebrew list will be synced up correctly and caching will be removed. Terminal.app is required since some apps need sudo permissions to remove services and files placed in system folders. Since other terminal apps don't support applescript and/or the 'do script' command, I opted to use the default macOS Terminal app for this.\n\nNOTE: If you undo the file delete with CMD+Z, the files will be put back but homebrew will not be aware of it. To get the homebrew list back in sync you'd need to run:\n brew install APPNAME --force")
}
// HStack(spacing: 0) {
// Text("Cleanup application: \(brewTerm)")
// .font(.footnote)
// .foregroundStyle(.primary.opacity(0.4))
//
// Button("") {
// selectBrewTerm()
// }
// .buttonStyle(SimpleButtonStyle(icon: "gear", help: "Set terminal application, right click to Reset", size: 14))
// .contextMenu{
// Button("Reset") {
// brewTerm = "Terminal"
// }
// }
// }
}
@@ -76,7 +56,7 @@ struct GeneralSettingsTab: View {
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -85,14 +65,14 @@ struct GeneralSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(confirmAlert ? "Confirmation alerts enabled" : "Confirmation alerts disabled")")
Text("Uninstall confirmation alerts")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
InfoButton(text: "When deleting files using the Trash button, you can prevent accidental deletions by showing an alert before proceeding with the action. Unrelated to the password prompt alert when deleting files in system folders.")
InfoButton(text: "When deleting files using the Trash button, you can prevent accidental deletions by showing an alert before proceeding with the action.")
Spacer()
Toggle(isOn: $confirmAlert, label: {
@@ -100,7 +80,7 @@ struct GeneralSettingsTab: View {
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -109,14 +89,14 @@ struct GeneralSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(oneShotMode ? "One-Shot Mode is enabled" : "One-Shot Mode is disabled")")
Text("Close after uninstall")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
InfoButton(text: "When one-shot mode is enabled, clicking the Uninstall button to remove an app will also close Pearcleaner right after. This only affects Pearcleaner when it is opened via external means, like Sentinel Trash Monitor or the Finder extension. This allows for single use of the app, AKA one-shot mode. When Pearcleaner is opened normally, this setting is ignored and will work as usual.")
InfoButton(text: "When this mode is enabled, clicking the Uninstall button to remove an app will also close Pearcleaner right after.\n This only affects Pearcleaner when it is opened via external means, like Sentinel Trash Monitor, Finder extension or a Deep Link.\nThis allows for single use of the app for a quick uninstall. When Pearcleaner is opened normally, this setting is ignored and will work as usual.")
Spacer()
Toggle(isOn: $oneShotMode, label: {
@@ -124,7 +104,7 @@ struct GeneralSettingsTab: View {
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -133,13 +113,12 @@ struct GeneralSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("File list sorting mode")
Text("File list sorting order")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
// InfoButton(text: "When searching for app files or leftover files, the list will be sorted either alphabetically or by size(large to small)")
Spacer()
Picker("", selection: $selectedSortAlpha) {
Text("Alphabetical")
@@ -150,7 +129,7 @@ struct GeneralSettingsTab: View {
.buttonStyle(.borderless)
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -159,13 +138,13 @@ struct GeneralSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("File size display mode")
Text("File size display options")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
InfoButton(text: "Real size type will show how much actual allocated space the file has on disk. Logical type shows the binary size. The filesystem can compress and deduplicate sectors on disk, so real size is sometimes smaller(or bigger) than logical size. Finder size is similar to if you right click > Get Info on a file in Finder, which will show both the logical and real sizes together.")
InfoButton(text: "Real size type will show how much actual allocated space the file has on disk.\n\nLogical type shows the binary size. The filesystem can compress and deduplicate sectors on disk, so real size is sometimes smaller(or bigger) than logical size.\n\nFinder size is similar to if you right click > Get Info on a file in Finder, which will show both the logical and real sizes together.")
Spacer()
Picker("", selection: $sizeType) {
Text("Real")
@@ -179,32 +158,28 @@ struct GeneralSettingsTab: View {
}
.padding(5)
.padding(.leading)
// === Sentinel =============================================================================================
Divider()
.padding()
HStack() {
Text("Sentinel Monitor").font(.title2)
Spacer()
}
.padding(.leading)
}
)
// === Sentinel =====================================================================================================
PearGroupBox(
header: { Text("Sentinel Monitor").font(.title2) },
content: {
HStack(spacing: 0) {
Image(systemName: sentinel ? "eye.circle" : "eye.slash.circle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(sentinel ? .green : .red)
.saturation(themeManager.displayMode.colorScheme == .dark ? 0.8 : 1)
Text(sentinel ? "Detecting when apps are moved to Trash" : "**NOT** detecting when apps are moved to Trash")
.foregroundStyle(.primary)
// .saturation(themeManager.displayMode.colorScheme == .dark ? 0.8 : 1)
Text("Detect when apps are moved to Trash")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
InfoButton(text: "When applications are moved to Trash, Pearcleaner will launch and find related files and folders for deletion.")
Spacer()
Toggle(isOn: $sentinel, label: {
@@ -220,40 +195,31 @@ struct GeneralSettingsTab: View {
}
.padding(5)
.padding(.leading)
})
// === Finder Extension =============================================================================================
Divider()
.padding()
HStack() {
Text("Finder Extension").font(.title2)
Spacer()
}
.padding(.leading)
PearGroupBox(
header: { Text("Finder Extension").font(.title2) },
content: {
HStack(spacing: 0) {
Image(systemName: appState.finderExtensionEnabled ? "puzzlepiece.extension.fill" : "puzzlepiece.extension")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(appState.finderExtensionEnabled ? .green : .red)
.saturation(themeManager.displayMode.colorScheme == .dark ? 0.8 : 1)
Text(appState.finderExtensionEnabled ? "Context menu extension for Finder is enabled" : "Context menu extension for Finder is disabled")
.foregroundStyle(.primary)
// .saturation(themeManager.displayMode.colorScheme == .dark ? 0.8 : 1)
Text("Enable context menu extension for Finder")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
InfoButton(text: "Enabling the extension will allow you to right click apps in Finder and quickly uninstall them with Pearcleaner")
.foregroundStyle(.primary)
InfoButton(text: "Enabling this extension will allow you to right click apps in Finder to quickly uninstall them with Pearcleaner")
Spacer()
// Button("Extensions") {
// FIFinderSyncController.showExtensionManagementInterface()
// }
// .buttonStyle(SimpleButtonStyle(icon: "folder", help: "Show Extensions Pane"))
// Button("Extensions") {
// FIFinderSyncController.showExtensionManagementInterface()
// }
// .buttonStyle(SimpleButtonStyle(icon: "folder", help: "Show Extensions Pane"))
Toggle(isOn: $appState.finderExtensionEnabled, label: {
})
@@ -269,32 +235,24 @@ struct GeneralSettingsTab: View {
}
.padding(5)
.padding(.leading)
// === CLI =============================================================================================
Divider()
.padding()
HStack() {
Text("Command Line").font(.title2)
Spacer()
}
.padding(.leading)
})
// === CLI ==========================================================================================================
PearGroupBox(
header: { Text("Command Line").font(.title2) },
content: {
HStack(spacing: 0) {
Image(systemName: "terminal")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(isCLISymlinked ? .green : .red)
.saturation(themeManager.displayMode.colorScheme == .dark ? 0.8 : 1)
Text(isCLISymlinked ? "Pearcleaner CLI is installed" : "Pearcleaner CLI is **not** installed")
.foregroundStyle(.primary)
// .saturation(themeManager.displayMode.colorScheme == .dark ? 0.8 : 1)
Text("Pearcleaner CLI support")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
InfoButton(text: "Enabling the CLI will allow you to execute Pearcleaner actions from the Terminal. This will add pearcleaner command into /usr/local/bin so it's available directly from your PATH environment variable. Try it after enabling:\n\n > pearcleaner --help")
.foregroundStyle(.primary)
InfoButton(text: "Enabling the CLI will allow you to execute Pearcleaner actions from the Terminal. This will add pearcleaner command into /usr/local/bin so it's available directly from your PATH environment variable. Try it after enabling:\n\n> pearcleaner --help")
Spacer()
Toggle(isOn: $isCLISymlinked, label: {
@@ -310,63 +268,14 @@ struct GeneralSettingsTab: View {
}
.padding(5)
.padding(.leading)
// === Reset Settings =============================================================================================
HStack() {
Spacer()
Button("") {
resetUserDefaults()
}
.buttonStyle(ResetSettingsButtonStyle(isResetting: $isResetting, label: "Reset Settings", help: "Reset all app settings to default"))
.disabled(isResetting)
Spacer()
}
.padding(.vertical, 10)
Spacer()
})
}
.onAppear {
appState.updateExtensionStatus()
isCLISymlinked = checkCLISymlink()
}
.padding(20)
.frame(width: 500, height: 640)
}
private func resetUserDefaults() {
isResetting = true
DispatchQueue.global(qos: .background).async {
UserDefaults.standard.dictionaryRepresentation().keys.forEach(UserDefaults.standard.removeObject(forKey:))
DispatchQueue.main.async {
isResetting = false
}
}
}
// private func selectBrewTerm() {
// let dialog = NSOpenPanel()
// dialog.title = "Select a Terminal application"
// dialog.allowedContentTypes = [UTType("com.apple.application-bundle")!] // Limit to .app bundles
// dialog.allowsMultipleSelection = false
// dialog.canChooseFiles = true
// dialog.canChooseDirectories = false
//
// if dialog.runModal() == .OK, let url = dialog.url {
// let appNameWithExtension = url.lastPathComponent
// brewTerm = appNameWithExtension.replacingOccurrences(of: ".app", with: "")
// }
// }
}
+45 -75
View File
@@ -46,25 +46,23 @@ struct InterfaceSettingsTab: View {
var body: some View {
VStack(spacing: 20) {
// === Appearance =================================================================================================
PearGroupBox(header: { Text("Appearance").font(.title2) },
content: {
VStack {
HStack() {
Text("Appearance").font(.title2)
Spacer()
}
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: glass ? "cube.transparent" : "cube.transparent.fill")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(glass ? "Transparent material enabled" : "Transparent material disabled")")
Text("Transparent material")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
Spacer()
Toggle(isOn: $glass, label: {
@@ -75,7 +73,6 @@ struct InterfaceSettingsTab: View {
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -84,11 +81,11 @@ struct InterfaceSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(animationEnabled ? "Animations enabled" : "Animations disabled")")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
Spacer()
Toggle(isOn: $animationEnabled, label: {
@@ -96,7 +93,6 @@ struct InterfaceSettingsTab: View {
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -105,11 +101,11 @@ struct InterfaceSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(minimalEnabled ? "Minimalist app list rows enabled" : "Minimalist app list rows disabled")")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
Spacer()
Toggle(isOn: $minimalEnabled, label: {
@@ -117,7 +113,6 @@ struct InterfaceSettingsTab: View {
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -126,11 +121,11 @@ struct InterfaceSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("Theme Mode")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
Spacer()
ThemeSettingsView(opacity: 1)
@@ -139,34 +134,24 @@ struct InterfaceSettingsTab: View {
}
}
.padding(5)
.padding(.leading)
// === Mini =================================================================================================
Divider()
.padding()
HStack() {
Text("Mini Configuration").font(.title2)
Spacer()
}
.padding(.leading)
})
// === Mini =======================================================================================================
PearGroupBox(header: { Text("Mini Configuration").font(.title2) },
content: {
VStack {
HStack(spacing: 0) {
Image(systemName: isVersionOrHigher(version: 14) ? "square.resize.up" : "macwindow.on.rectangle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(mini ? "Mini window mode enabled" : "Mini window mode disabled")")
Text("Mini window mode")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
InfoButton(text: "Toggling between modes will reset the window frames to their default size/position")
Spacer()
@@ -212,7 +197,6 @@ struct InterfaceSettingsTab: View {
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -221,11 +205,11 @@ struct InterfaceSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(miniView ? "Show apps list on startup" : "Show drop target on startup")")
Text("Show apps list on startup")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.")
@@ -239,7 +223,6 @@ struct InterfaceSettingsTab: View {
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -248,14 +231,14 @@ struct InterfaceSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(popoverStay ? "Popover window will stay on top" : "Popover window will not stay on top")")
Text("Pin popover window on top")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the popover.")
InfoButton(text: "In mini window mode, if you pin the Files popover on top, clicking away from the window will not dismiss it. Otherwise, it will dismiss by clicking anywhere outside the app.")
Spacer()
Toggle(isOn: $popoverStay, label: {
@@ -263,32 +246,24 @@ struct InterfaceSettingsTab: View {
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
// === MenuBar===============================================================================================
Divider()
.padding()
HStack() {
Text("Menubar").font(.title2)
Spacer()
}
.padding(.leading)
})
// === Menubar ====================================================================================================
PearGroupBox(header: { Text("Menubar").font(.title2) },
content: {
VStack {
HStack(spacing: 0) {
Image(systemName: "menubar.rectangle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(menubarEnabled ? "Menubar icon enabled" : "Menubar icon disabled")")
Text("Menubar icon mode")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
InfoButton(text: "When menubar icon is enabled, the main app window and dock icon will be disabled since the app will be put in accessory mode.")
Spacer()
@@ -343,7 +318,6 @@ struct InterfaceSettingsTab: View {
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -352,11 +326,11 @@ struct InterfaceSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("\(isLaunchAtLoginEnabled ? "Launch at login enabled" : "Launch at login disabled")")
Text("Launch Pearcleaner at login")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
InfoButton(text: "This setting will affect Pearcleaner whether you're running in menubar mode or regular mode. If you disable menubar icon, you might want to disable this as well so Pearcleaner doesn't start on login.")
Spacer()
@@ -384,7 +358,6 @@ struct InterfaceSettingsTab: View {
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
@@ -393,11 +366,11 @@ struct InterfaceSettingsTab: View {
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
VStack(alignment: .leading, spacing: 5) {
Text("Menubar icon")
Text("Menubar icon preference")
.font(.callout)
.foregroundStyle(.primary.opacity(0.5))
.foregroundStyle(.primary)
}
Spacer()
Picker("", selection: $selectedMenubarIcon) {
@@ -419,13 +392,10 @@ struct InterfaceSettingsTab: View {
}
.padding(5)
.padding(.leading)
Spacer()
}
})
}
.padding(20)
.frame(width: 500, height: 600)
}
+259 -32
View File
@@ -17,53 +17,143 @@ struct SettingsView: View {
@Binding var search: String
@AppStorage("settings.general.glass") private var glass: Bool = false
@AppStorage("settings.general.selectedTab") private var selectedTab: CurrentTabView = .general
@State private var isResetting = false
var body: some View {
TabView(selection: $selectedTab) {
NavigationStack {
HStack(spacing: 0) {
sidebarView
detailView
}
}
.navigationTitle("")
.frame(width: 750, height: 620)
}
/// Sidebar view for navigation items
@ViewBuilder
private var sidebarView: some View {
VStack(alignment: .center, spacing: 4) {
PearDropViewSmall()
.padding(.leading, 4)
Divider()
.padding(.bottom, 8)
SidebarItemView(title: "General", systemImage: "gear", isSelected: selectedTab == .general) {
selectedTab = .general
}
SidebarItemView(title: "Interface", systemImage: "macwindow", isSelected: selectedTab == .interface) {
selectedTab = .interface
}
SidebarItemView(title: "Folders", systemImage: "folder", isSelected: selectedTab == .folders) {
selectedTab = .folders
}
SidebarItemView(title: "Update", systemImage: "cloud", isSelected: selectedTab == .update) {
selectedTab = .update
}
SidebarItemView(title: "Tips", systemImage: "star", isSelected: selectedTab == .tips) {
selectedTab = .tips
}
SidebarItemView(title: "About", systemImage: "info.circle", isSelected: selectedTab == .about) {
selectedTab = .about
}
Spacer()
Text("v\(Bundle.main.version)").foregroundStyle(.secondary)
.padding(.bottom, 4)
Button("") {
resetUserDefaults()
}
.buttonStyle(ResetSettingsButtonStyle(isResetting: $isResetting, label: "Reset Settings", help: "Reset all settings to default"))
.disabled(isResetting)
}
.padding(.bottom)
.padding(.horizontal, 8)
.frame(width: 200)
.background(backgroundView(themeManager: themeManager, darker: true, glass: glass))
}
/// Detail view content based on the selected tab
@ViewBuilder
private var detailView: some View {
ScrollView() {
// The actual detail views wrapped inside the VStack
switch selectedTab {
case .general:
GeneralSettingsTab()
.tabItem {
Label(CurrentTabView.general.title, systemImage: "gear")
}
.tag(CurrentTabView.general)
.environmentObject(appState)
case .interface:
InterfaceSettingsTab(showPopover: $showPopover, search: $search)
.tabItem {
Label(CurrentTabView.interface.title, systemImage: "macwindow")
}
.tag(CurrentTabView.interface)
.environmentObject(themeManager)
case .folders:
FolderSettingsTab()
.tabItem {
Label(CurrentTabView.folders.title, systemImage: "folder")
}
.tag(CurrentTabView.folders)
.environmentObject(themeManager)
case .update:
UpdateSettingsTab()
.tabItem {
Label(CurrentTabView.update.title, systemImage: "cloud")
}
.tag(CurrentTabView.update)
.environmentObject(themeManager)
.environmentObject(updater)
case .tips:
TipsSettingsTab()
.tabItem {
Label(CurrentTabView.tips.title, systemImage: "star")
}
.tag(CurrentTabView.tips)
case .about:
AboutSettingsTab()
.tabItem {
Label(CurrentTabView.about.title, systemImage: "info.circle")
}
.tag(CurrentTabView.about)
}
.background(backgroundView(themeManager: themeManager, glass: glass))
.scrollIndicators(.automatic)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
.offset(y: -22)
.background(backgroundView(themeManager: themeManager, glass: false))
}
private func resetUserDefaults() {
isResetting = true
DispatchQueue.global(qos: .background).async {
UserDefaults.standard.dictionaryRepresentation().keys.forEach(UserDefaults.standard.removeObject(forKey:))
DispatchQueue.main.async {
isResetting = false
}
}
}
}
struct SidebarItemView: View {
var title: String
var systemImage: String
var isSelected: Bool
var onTap: () -> Void
var body: some View {
HStack(spacing: 14) {
Image(systemName: systemImage)
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(isSelected ? .accentColor : .primary)
.frame(width: 20, height: 20)
Text(title)
.font(.system(size: 14, weight: .regular))
.foregroundColor(isSelected ? .accentColor : .primary)
Spacer()
}
.padding(.vertical, 8)
.padding(.leading, 8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(isSelected ? .primary.opacity(0.2) : Color.clear)
.cornerRadius(6)
.contentShape(Rectangle())
.onTapGesture {
onTap()
}
}
}
@@ -72,5 +162,142 @@ struct SettingsView: View {
// NavigationStack {
// HStack(spacing: 0) {
// // Custom Sidebar with buttons for each tab
// List(selection: $selectedTab) {
// SidebarItemView(title: CurrentTabView.general.title,
// systemImage: "gear",
// isSelected: selectedTab == .general) {
// selectedTab = .general
// }
//
// SidebarItemView(title: CurrentTabView.interface.title,
// systemImage: "macwindow",
// isSelected: selectedTab == .interface) {
// selectedTab = .interface
// }
//
// SidebarItemView(title: CurrentTabView.folders.title,
// systemImage: "folder",
// isSelected: selectedTab == .folders) {
// selectedTab = .folders
// }
//
// SidebarItemView(title: CurrentTabView.update.title,
// systemImage: "cloud",
// isSelected: selectedTab == .update) {
// selectedTab = .update
// }
//
// SidebarItemView(title: CurrentTabView.tips.title,
// systemImage: "star",
// isSelected: selectedTab == .tips) {
// selectedTab = .tips
// }
//
// SidebarItemView(title: CurrentTabView.about.title,
// systemImage: "info.circle",
// isSelected: selectedTab == .about) {
// selectedTab = .about
// }
//
// Spacer() // Pushes items to the top
// }
// .removeSidebarToggle()
// .frame(width: 200) // Fixed width for sidebar
// .padding(.top, 20) // Top padding to space out items
// .background(backgroundView(themeManager: themeManager, glass: true))
//
// ZStack(alignment: .top) {
// // Custom Toolbar View at the top
// HStack {
// Text(selectedTab.title)
// .font(.headline)
// .padding(.leading)
//
// Spacer()
//
// // Add more toolbar buttons as needed
// Button(action: {
// print("Toolbar Button Tapped")
// }) {
// Image(systemName: "gearshape")
// .padding()
// }
// }
// .frame(height: 40) // Set fixed height for toolbar
// .background(backgroundView(themeManager: themeManager, glass: true))
// .zIndex(1) // Ensure this is on top
// .offset(y: -30)
//
// // The actual detail view content
// Group {
// switch selectedTab {
// case .general:
// GeneralSettingsTab()
// case .interface:
// InterfaceSettingsTab(showPopover: $showPopover, search: $search)
// .environmentObject(themeManager)
// case .folders:
// FolderSettingsTab()
// .environmentObject(themeManager)
// case .update:
// UpdateSettingsTab()
// .environmentObject(themeManager)
// case .tips:
// TipsSettingsTab()
// case .about:
// AboutSettingsTab()
// }
// }
// .padding(.top, 40) // Add padding below the custom toolbar
// .frame(maxWidth: .infinity, maxHeight: .infinity) // Fill remaining space
// .background(backgroundView(themeManager: themeManager, glass: false))
// }
//
// }
// .navigationTitle("")
// }
// TabView(selection: $selectedTab) {
// GeneralSettingsTab()
// .tabItem {
// Label(CurrentTabView.general.title, systemImage: "gear")
// }
// .tag(CurrentTabView.general)
//
// InterfaceSettingsTab(showPopover: $showPopover, search: $search)
// .tabItem {
// Label(CurrentTabView.interface.title, systemImage: "macwindow")
// }
// .tag(CurrentTabView.interface)
// .environmentObject(themeManager)
//
// FolderSettingsTab()
// .tabItem {
// Label(CurrentTabView.folders.title, systemImage: "folder")
// }
// .tag(CurrentTabView.folders)
// .environmentObject(themeManager)
//
// UpdateSettingsTab()
// .tabItem {
// Label(CurrentTabView.update.title, systemImage: "cloud")
// }
// .tag(CurrentTabView.update)
// .environmentObject(themeManager)
//
// TipsSettingsTab()
// .tabItem {
// Label(CurrentTabView.tips.title, systemImage: "star")
// }
// .tag(CurrentTabView.tips)
//
// AboutSettingsTab()
// .tabItem {
// Label(CurrentTabView.about.title, systemImage: "info.circle")
// }
// .tag(CurrentTabView.about)
// }
// .background(backgroundView(themeManager: themeManager, glass: glass))
+6 -10
View File
@@ -18,6 +18,7 @@ struct TipsSettingsTab: View {
var body: some View {
VStack {
PearGroupBox(header: { Text("Tips And Tricks").font(.title2) }, content: {
ScrollView {
VStack(alignment: .leading, spacing: 15) {
Text("1. Clicking an app will perform a search for files and folders, which will be displayed in the Files view")
@@ -30,27 +31,22 @@ struct TipsSettingsTab: View {
Text("8. CMD + R will refresh the app list. This can also be done from the refresh button in the search bar")
Text("9. In the search bar menu button, you will also find Leftover Files which will find files leftover by previously uninstalled applications")
Text("10. You can enable a menubar icon from Settings")
Text("11. You can enable UI mini mode from Settings")
Text("11. You can enable mini mode version of the UI from Settings")
Text("12. You can enable a Finder extension from Settings which allows you to uninstall apps by right clicking them in Finder")
Text("13. You can enable Homebrew cleanup from Settings")
Text("14. You can change the app theme colors from Settings")
Text("15. When there's new features available, a new features badge will show at the top of the app list. Same for app updates or missing permissions")
Text("15. When there's new features available, an announcement badge will show at the top of the app list. Same for app app updates or missing permissions")
Text("16. You can disable animations in the Settings Interface tab plus a few more other options like confirmation dialog when removing files")
}
.padding()
}
.scrollIndicators(.visible)
.frame(height: 500)
.frame(height: 530)
.frame(maxWidth: .infinity)
.background(backgroundView(themeManager: themeManager, darker: true, glass: glass))
.clipShape(RoundedRectangle(cornerRadius: 8))
.padding(.bottom)
Spacer()
})
}
.padding(20)
.frame(width: 500, height: 550)
}
}
+10 -12
View File
@@ -17,23 +17,26 @@ struct UpdateSettingsTab: View {
@AppStorage("settings.general.glass") private var glass: Bool = false
var body: some View {
VStack {
VStack(spacing: 20) {
// === Frequency ============================================================================================
PearGroupBox(header: { Text("Update Frequency").font(.title2) }, content: {
FrequencyView(updater: updater)
.padding(5)
.padding(.horizontal)
})
// === Release Notes ========================================================================================
PearGroupBox(header: { Text("Release Notes").font(.title2) }, content: {
ReleasesView(updater: updater)
.frame(height: 400)
.frame(height: 380)
.frame(maxWidth: .infinity)
.background(backgroundView(themeManager: themeManager, darker: true, glass: glass))
.clipShape(RoundedRectangle(cornerRadius: 8))
})
// === Buttons ==============================================================================================
HStack(alignment: .center, spacing: 20) {
Button(""){
updater.checkForUpdates(showSheet: false)
updater.checkForUpdatesForce(showSheet: false)
}
.buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", label: "Refresh", help: "Refresh updater"))
@@ -50,13 +53,8 @@ struct UpdateSettingsTab: View {
.buttonStyle(SimpleButtonStyle(icon: "link", label: "Releases", help: "View releases on GitHub"))
}
.padding(.top, 5)
Spacer()
}
.padding(20)
.frame(width: 500, height: 540)
}
+1
View File
@@ -159,6 +159,7 @@ struct AppListItems: View {
DispatchQueue.global(qos: .userInitiated).async {
let size = totalSizeOnDisk(for: appInfo.path).logical
DispatchQueue.main.async {
print("Sizing up")
self.bundleSize = size
// Update the appInfo in the appState array
if let index = appState.sortedApps.firstIndex(where: { $0.path == appInfo.path }) {
+1 -2
View File
@@ -123,8 +123,7 @@ struct AppSearchView: View {
Button("Settings") {
if #available(macOS 13.0, *) {
NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
}
else {
} else {
NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil)
}
showMenu = false