This commit is contained in:
Alin
2024-03-18 18:05:13 -06:00
parent 404bdf0776
commit 2318949caf
28 changed files with 865 additions and 452 deletions
+1 -1
View File
@@ -152,7 +152,7 @@ struct AboutSettingsTab: View {
Text("Made with ❤️ by Alin Lupascu (dev@itsalin.com)").font(.footnote).padding(.bottom)
}
.padding(20)
.frame(width: 500, height: 650)
.frame(width: 500, height: 600)
}
}
+2 -163
View File
@@ -35,32 +35,11 @@ struct GeneralSettingsTab: View {
VStack {
HStack() {
Text("Appearance").font(.title2)
Text("Functionality").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(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(glass ? "Transparent material enabled" : "Transparent material disabled")")
.font(.callout)
.foregroundStyle(.gray)
}
// InfoButton(text: "When transparent material is enabled, sticky section headers (User/System) in app list are disabled to keep from showing app name text overlayed under the section header text with no background to separate the two.", color: nil)
Spacer()
Toggle(isOn: $glass, label: {
})
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: instantSearch ? "bolt" : "bolt.slash")
@@ -110,146 +89,6 @@ struct GeneralSettingsTab: View {
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: displayMode.colorScheme == .dark ? "moon.fill" : "sun.max.fill")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("Set application color mode")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Picker("", selection: $selectedTheme) {
ForEach(themes, id: \.self) { theme in
Text(theme)
}
}
.pickerStyle(SegmentedPickerStyle())
.frame(width: 200)
.onChange(of: selectedTheme) { newTheme in
switch newTheme {
case "Auto":
displayMode.colorScheme = nil
if isDarkModeEnabled() {
displayMode.colorScheme = .dark
} else {
displayMode.colorScheme = .light
}
case "Dark":
displayMode.colorScheme = .dark
case "Light":
displayMode.colorScheme = .light
default:
break
}
}
}
.padding(5)
.padding(.leading)
// === Mini =================================================================================================
Divider()
.padding()
HStack() {
Text("Mini Configuration").font(.title2)
Spacer()
}
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: mini ? "square.resize.up" : "square.resize.down")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(mini ? "Mini window mode selected" : "Full size window mode selected")")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $mini, label: {
})
.toggleStyle(.switch)
.onChange(of: mini) { newVal in
if mini {
appState.currentView = miniView ? .apps : .empty
showPopover = false
resizeWindowAuto(windowSettings: windowSettings)
} else {
resizeWindowAuto(windowSettings: windowSettings)
if appState.appInfo.appName.isEmpty {
appState.currentView = .empty
} else {
appState.currentView = .files
}
}
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: miniView ? "square.grid.3x3.square" : "plus.square.dashed")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(miniView ? "Show apps list on startup" : "Show drop target on startup")")
.font(.callout)
.foregroundStyle(.gray)
}
InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.", color: nil, label: "")
Spacer()
Toggle(isOn: $miniView, label: {
})
.toggleStyle(.switch)
.onChange(of: miniView) { newVal in
appState.currentView = newVal ? .apps : .empty
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: popoverStay ? "pin" : "pin.slash")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(popoverStay ? "Popover window will stay on top" : "Popover window will not stay on top")")
.font(.callout)
.foregroundStyle(.gray)
}
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.", color: nil, label: "")
Spacer()
Toggle(isOn: $popoverStay, label: {
})
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
// === Perms ================================================================================================
@@ -369,7 +208,7 @@ struct GeneralSettingsTab: View {
}
.padding(20)
.frame(width: 500, height: 690)
.frame(width: 500, height: 420)
}
+396
View File
@@ -0,0 +1,396 @@
//
// Interface.swift
// Pearcleaner
//
// Created by Alin Lupascu on 3/18/24.
//
import Foundation
import SwiftUI
import ServiceManagement
struct InterfaceSettingsTab: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations
@State private var windowSettings = WindowSettings()
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
// @AppStorage("settings.dock.enabled") private var dockEnabled: Bool = false
@AppStorage("settings.general.mini") private var mini: Bool = false
@AppStorage("displayMode") var displayMode: DisplayMode = .system
@AppStorage("settings.general.glass") private var glass: Bool = true
@AppStorage("settings.general.dark") var isDark: Bool = true
@AppStorage("settings.general.popover") private var popoverStay: Bool = true
@AppStorage("settings.general.miniview") private var miniView: Bool = true
@AppStorage("settings.general.selectedTheme") var selectedTheme: String = "Auto"
@AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "pear-4"
private let themes = ["Auto", "Dark", "Light"]
@State private var isLaunchAtLoginEnabled: Bool = false
let icons = ["externaldrive", "trash", "folder", "pear-1", "pear-1.5", "pear-2", "pear-3", "pear-4"]
@Binding var showPopover: Bool
@Binding var search: String
var body: some View {
Form {
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(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(glass ? "Transparent material enabled" : "Transparent material disabled")")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $glass, label: {
})
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: displayMode.colorScheme == .dark ? "moon.fill" : "sun.max.fill")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("Application color mode")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Picker("", selection: $selectedTheme) {
ForEach(themes, id: \.self) { theme in
Text(theme)
}
}
.pickerStyle(SegmentedPickerStyle())
.frame(width: 200)
.onChange(of: selectedTheme) { newTheme in
switch newTheme {
case "Auto":
displayMode.colorScheme = nil
if isDarkModeEnabled() {
displayMode.colorScheme = .dark
} else {
displayMode.colorScheme = .light
}
case "Dark":
displayMode.colorScheme = .dark
case "Light":
displayMode.colorScheme = .light
default:
break
}
}
}
.padding(5)
.padding(.leading)
// === Mini =================================================================================================
Divider()
.padding()
HStack() {
Text("Mini Configuration").font(.title2)
Spacer()
}
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: mini ? "square.resize.up" : "square.resize.down")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(mini ? "Mini window mode" : "Full size window mode")")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $mini, label: {
})
.toggleStyle(.switch)
.onChange(of: mini) { newVal in
if mini {
appState.currentView = miniView ? .apps : .empty
showPopover = false
resizeWindowAuto(windowSettings: windowSettings)
} else {
resizeWindowAuto(windowSettings: windowSettings)
if appState.appInfo.appName.isEmpty {
appState.currentView = .empty
} else {
appState.currentView = .files
}
}
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: miniView ? "square.grid.3x3.square" : "plus.square.dashed")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(miniView ? "Show apps list on startup" : "Show drop target on startup")")
.font(.callout)
.foregroundStyle(.gray)
}
InfoButton(text: "In mini window mode, you can have Pearcleaner startup to the Apps List view or the Drop Target view.", color: nil, label: "")
Spacer()
Toggle(isOn: $miniView, label: {
})
.toggleStyle(.switch)
.onChange(of: miniView) { newVal in
appState.currentView = newVal ? .apps : .empty
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: popoverStay ? "pin" : "pin.slash")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(popoverStay ? "Popover window will stay on top" : "Popover window will not stay on top")")
.font(.callout)
.foregroundStyle(.gray)
}
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.", color: nil, label: "")
Spacer()
Toggle(isOn: $popoverStay, label: {
})
.toggleStyle(.switch)
}
.padding(5)
.padding(.leading)
// === MenuBar===============================================================================================
Divider()
.padding()
HStack() {
Text("Menubar").font(.title2)
Spacer()
}
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: "menubar.rectangle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(menubarEnabled ? "Menubar icon enabled" : "Menubar icon disabled")")
.font(.callout)
.foregroundStyle(.gray)
}
InfoButton(text: "When menubar icon is enabled, the main app window and dock icon will be hidden. You can still pop-out the main app window from the menubar icon temporarily if you'd like.", color: nil, label: "")
Spacer()
Toggle(isOn: $menubarEnabled, label: {
})
.toggleStyle(.switch)
.onChange(of: menubarEnabled) { newVal in
if newVal {
MenuBarExtraManager.shared.addMenuBarExtra(withView: {
MenuBarMiniAppView(search: $search, showPopover: $showPopover)
.environmentObject(locations)
.environmentObject(appState)
}, icon: selectedMenubarIcon)
NSApplication.shared.setActivationPolicy(.accessory)
findAndShowWindows(named: ["Pearcleaner", "Interface"])
} else {
MenuBarExtraManager.shared.removeMenuBarExtra()
// dockEnabled = true
NSApplication.shared.setActivationPolicy(.regular)
if !hasWindowOpen() {
if mini {
windowSettings.newWindow {
MiniMode(search: $search, showPopover: $showPopover)
.environmentObject(locations)
.environmentObject(appState)
}
} else {
windowSettings.newWindow {
RegularMode(search: $search, showPopover: $showPopover)
.environmentObject(locations)
.environmentObject(appState)
}
}
}
}
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: isLaunchAtLoginEnabled ? "person" : "person.slash")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(isLaunchAtLoginEnabled ? "Launch at login enabled" : "Launch at login disabled")")
.font(.callout)
.foregroundStyle(.gray)
}
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.", color: nil, label: "")
Spacer()
Toggle(isOn: $isLaunchAtLoginEnabled, label: {
})
.toggleStyle(.switch)
.onAppear {
isLaunchAtLoginEnabled = SMAppService.mainApp.status == .enabled
}
.onChange(of: isLaunchAtLoginEnabled) { newValue in
do {
if newValue {
if SMAppService.mainApp.status == .enabled {
try? SMAppService.mainApp.unregister()
}
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
} catch {
printOS("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
}
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: "paintbrush")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("Menubar icon")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Picker("", selection: $selectedMenubarIcon) {
ForEach(icons, id: \.self) { icon in
HStack {
if icon.contains("pear") {
Image(icon)
.resizable()
.scaledToFit()
} else {
Image(systemName: icon)
.resizable()
.scaledToFit()
}
}
.tag(icon)
}
}
.frame(width: 60)
.onChange(of: selectedMenubarIcon) { newValue in
MenuBarExtraManager.shared.swapMenuBarIcon(icon: newValue)
}
}
.padding(5)
.padding(.leading)
// HStack(spacing: 0) {
// Image(systemName: "dock.rectangle")
// .resizable()
// .scaledToFit()
// .frame(width: 20, height: 20)
// .padding(.trailing)
// .foregroundStyle(.gray)
// VStack(alignment: .leading, spacing: 5) {
// Text("\(dockEnabled ? "Show dock icon" : "Hide dock icon")")
// .font(.callout)
// .foregroundStyle(.gray)
// }
// InfoButton(text: "This setting only affects Pearcleaner when the menubar icon is enabled, otherwise the dock icon will always show", color: nil, label: "")
// Spacer()
// Toggle(isOn: $dockEnabled, label: {
// })
// .toggleStyle(.switch)
// .onChange(of: dockEnabled) { newValue in
// if newValue {
// NSApplication.shared.setActivationPolicy(.regular)
// } else {
// if menubarEnabled {
// NSApplication.shared.setActivationPolicy(.accessory)
// }
//
// }
// }
//
// }
// .padding(5)
// .padding(.leading)
Spacer()
}
}
.padding(20)
.frame(width: 500, height: 520)
}
}
-112
View File
@@ -1,112 +0,0 @@
//
// MenuBar.swift
// Pearcleaner
//
// Created by Alin Lupascu on 3/16/24.
//
import Foundation
import SwiftUI
import ServiceManagement
struct MenuBarSettingsTab: View {
@EnvironmentObject var appState: AppState
@EnvironmentObject var locations: Locations
@AppStorage("settings.menubar.enabled") private var menubarEnabled: Bool = false
@State private var isLaunchAtLoginEnabled: Bool = false
@Binding var showPopover: Bool
@Binding var search: String
var body: some View {
Form {
VStack {
HStack() {
Text("Configuration").font(.title2)
Spacer()
}
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: "menubar.rectangle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(menubarEnabled ? "Menubar icon enabled" : "Menubar icon disabled")")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $menubarEnabled, label: {
})
.toggleStyle(.switch)
.onChange(of: menubarEnabled) { newVal in
if newVal {
MenuBarExtraManager.shared.addMenuBarExtra {
MenuBarMiniAppView(search: $search, showPopover: $showPopover)
.environmentObject(locations)
.environmentObject(appState)
}
} else {
MenuBarExtraManager.shared.removeMenuBarExtra()
}
}
}
.padding(5)
.padding(.leading)
HStack(spacing: 0) {
Image(systemName: isLaunchAtLoginEnabled ? "person.crop.circle.badge.checkmark" : "person.crop.circle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.trailing)
.foregroundStyle(.gray)
VStack(alignment: .leading, spacing: 5) {
Text("\(isLaunchAtLoginEnabled ? "Launch at login enabled" : "Launch at login disabled")")
.font(.callout)
.foregroundStyle(.gray)
}
Spacer()
Toggle(isOn: $isLaunchAtLoginEnabled, label: {
})
.toggleStyle(.switch)
.onAppear {
isLaunchAtLoginEnabled = SMAppService.mainApp.status == .enabled
}
.onChange(of: isLaunchAtLoginEnabled) { newValue in
do {
if newValue {
if SMAppService.mainApp.status == .enabled {
try? SMAppService.mainApp.unregister()
}
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
} catch {
printOS("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
}
}
}
.padding(5)
.padding(.leading)
Spacer()
}
}
.padding(20)
.frame(width: 500, height: 690)
}
}
+3 -3
View File
@@ -23,11 +23,11 @@ struct SettingsView: View {
}
.tag(CurrentTabView.general)
MenuBarSettingsTab(showPopover: $showPopover, search: $search)
InterfaceSettingsTab(showPopover: $showPopover, search: $search)
.tabItem {
Label(CurrentTabView.menubar.title, systemImage: "menubar.rectangle")
Label(CurrentTabView.interface.title, systemImage: "macwindow")
}
.tag(CurrentTabView.menubar)
.tag(CurrentTabView.interface)
UpdateSettingsTab(showFeature: $showFeature)
.tabItem {
+1 -1
View File
@@ -107,7 +107,7 @@ struct UpdateSettingsTab: View {
}
.padding(20)
.frame(width: 500, height: 650)
.frame(width: 500, height: 520)
}
}