mirror of
https://github.com/exituser/Pearcleaner.git
synced 2026-09-17 10:48:52 +00:00
v3.5.0
This commit is contained in:
@@ -41,15 +41,16 @@ struct AppCommands: Commands {
|
||||
let sortedApps = getSortedApps(paths: fsm.folderPaths, appState: appState)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
appState.sortedApps = []
|
||||
// appState.sortedApps.systemApps = []
|
||||
appState.sortedApps = sortedApps
|
||||
// appState.sortedApps.systemApps = sortedApps.systemApps
|
||||
if instantSearch {
|
||||
Task(priority: .high){
|
||||
loadAllPaths(allApps: sortedApps, appState: appState, locations: locations)
|
||||
loadAllPaths(allApps: sortedApps, appState: appState, locations: locations) {
|
||||
appState.reload.toggle()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
appState.reload.toggle()
|
||||
}
|
||||
appState.reload.toggle()
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import FinderSync
|
||||
|
||||
let home = FileManager.default.homeDirectoryForCurrentUser.path
|
||||
|
||||
@@ -31,6 +32,7 @@ class AppState: ObservableObject
|
||||
@Published var popCount: Int = 0
|
||||
@Published var instantProgress: Double = 0.0
|
||||
@Published var instantTotal: Double = 0.0
|
||||
@Published var finderExtensionEnabled: Bool = false
|
||||
|
||||
|
||||
init() {
|
||||
@@ -54,6 +56,21 @@ class AppState: ObservableObject
|
||||
fileSize: [:],
|
||||
fileIcon: [:]
|
||||
)
|
||||
updateExtensionStatus()
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(updateExtensionStatus),
|
||||
name: NSApplication.didBecomeActiveNotification,
|
||||
object: nil
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@objc func updateExtensionStatus() {
|
||||
let extentionStatus = FIFinderSyncController.isExtensionEnabled
|
||||
DispatchQueue.main.async {
|
||||
self.finderExtensionEnabled = extentionStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ class DeeplinkManager {
|
||||
// This handles dropping an app onto Pearcleaner
|
||||
if url.pathExtension == "app" {
|
||||
handleAppBundle(url: url, appState: appState, locations: locations)
|
||||
// This handles sentinel monitor launch
|
||||
} else if url.scheme == DeepLinkConstants.scheme,
|
||||
url.host == DeepLinkConstants.host,
|
||||
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||
|
||||
@@ -11,15 +11,17 @@ import SwiftUI
|
||||
struct SimpleButtonStyle: ButtonStyle {
|
||||
@State private var hovered = false
|
||||
let icon: String
|
||||
let label: String?
|
||||
let label: String
|
||||
let help: String
|
||||
let color: Color
|
||||
let size: CGFloat
|
||||
|
||||
init(icon: String, label: String? = "", help: String, color: Color) {
|
||||
init(icon: String, label: String = "", help: String, color: Color = Color("mode"), size: CGFloat = 20) {
|
||||
self.icon = icon
|
||||
self.label = label
|
||||
self.help = help
|
||||
self.color = color
|
||||
self.size = size
|
||||
}
|
||||
|
||||
func makeBody(configuration: Self.Configuration) -> some View {
|
||||
@@ -27,8 +29,8 @@ struct SimpleButtonStyle: ButtonStyle {
|
||||
Image(systemName: icon)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20)
|
||||
if let label = label, !label.isEmpty {
|
||||
.frame(width: size, height: size)
|
||||
if !label.isEmpty {
|
||||
Text(label)
|
||||
}
|
||||
}
|
||||
@@ -45,34 +47,6 @@ struct SimpleButtonStyle: ButtonStyle {
|
||||
}
|
||||
|
||||
|
||||
struct NavButtonBottomBarStyle: ButtonStyle {
|
||||
@State private var isHovered = false
|
||||
var image: String
|
||||
var help: String
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
ZStack {
|
||||
Image(systemName: image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundStyle(isHovered ? Color("mode").opacity(0.8) : Color("mode").opacity(0.5))
|
||||
Rectangle()
|
||||
.foregroundColor(.clear)
|
||||
.frame(width: 36, height: 36)
|
||||
.cornerRadius(6)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
||||
.buttonStyle(.plain)
|
||||
.onHover { inside in
|
||||
isHovered = inside
|
||||
}
|
||||
.help(help)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct InfoButton: View {
|
||||
@State private var isPopoverPresented: Bool = false
|
||||
@@ -215,7 +189,7 @@ public struct NewFeatureView: View {
|
||||
showFeature = false
|
||||
}
|
||||
}
|
||||
.buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close", color: .gray))
|
||||
.buttonStyle(SimpleButtonStyle(icon: "x.circle.fill", help: "Close", color: (Color("mode").opacity(0.5))))
|
||||
.onHover { isHovered in
|
||||
if isHovered {
|
||||
NSCursor.pointingHand.push()
|
||||
@@ -249,43 +223,36 @@ public struct NewFeatureView: View {
|
||||
|
||||
|
||||
public struct SlideableDivider: View {
|
||||
@EnvironmentObject private var themeSettings: ThemeSettings
|
||||
@Binding var dimension: Double
|
||||
@State private var dimensionStart: Double?
|
||||
@State private var handleWidth: Double = 2
|
||||
@State private var handleWidth: Double = 4
|
||||
@State private var handleHeight: Double = 30
|
||||
@State private var isHovered: Bool = false
|
||||
public init(dimension: Binding<Double>) {
|
||||
self._dimension = dimension
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Rectangle()
|
||||
.foregroundStyle(Color.gray.opacity(0.3))
|
||||
.frame(width: 1)
|
||||
.overlay(
|
||||
VStack(spacing: 0) {
|
||||
Spacer()
|
||||
RoundedRectangle(cornerRadius: 50)
|
||||
.fill(Color("mode").opacity(0.3))
|
||||
.frame(width: handleWidth, height: handleHeight)
|
||||
.onHover { inside in
|
||||
if inside {
|
||||
NSCursor.resizeLeftRight.push()
|
||||
.foregroundStyle(Color("mode").opacity(0.0))
|
||||
.frame(width: 10)
|
||||
.onHover { inside in
|
||||
if inside {
|
||||
NSCursor.resizeLeftRight.push()
|
||||
|
||||
} else {
|
||||
NSCursor.pop()
|
||||
} else {
|
||||
NSCursor.pop()
|
||||
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
Button("Reset Size") {
|
||||
dimension = 280
|
||||
}
|
||||
}
|
||||
.gesture(drag)
|
||||
Spacer()
|
||||
}
|
||||
.offset(x: 5)
|
||||
)
|
||||
}
|
||||
.contextMenu {
|
||||
Button("Reset Size") {
|
||||
dimension = 280
|
||||
}
|
||||
}
|
||||
.gesture(drag)
|
||||
.help("Right click to reset size")
|
||||
}
|
||||
|
||||
var drag: some Gesture {
|
||||
@@ -302,13 +269,13 @@ public struct SlideableDivider: View {
|
||||
let maxWidth: Double = 350
|
||||
dimension = max(minWidth, min(maxWidth, newDimension))
|
||||
NSCursor.closedHand.set()
|
||||
handleWidth = 4
|
||||
handleWidth = 6
|
||||
handleHeight = 40
|
||||
}
|
||||
.onEnded { val in
|
||||
dimensionStart = nil
|
||||
NSCursor.arrow.set()
|
||||
handleWidth = 2
|
||||
handleWidth = 4
|
||||
handleHeight = 30
|
||||
}
|
||||
}
|
||||
@@ -323,18 +290,18 @@ struct LabeledDivider: View {
|
||||
HStack(spacing: 0) {
|
||||
Rectangle()
|
||||
.frame(height: 1)
|
||||
.foregroundColor(.gray.opacity(0.2))
|
||||
.foregroundColor(Color("mode").opacity(0.2))
|
||||
|
||||
Text(label)
|
||||
.textCase(.uppercase)
|
||||
.font(.title2)
|
||||
.foregroundColor(.gray.opacity(0.6))
|
||||
.foregroundColor(Color("mode").opacity(0.6))
|
||||
.padding(.horizontal, 10)
|
||||
.frame(minWidth: 80)
|
||||
|
||||
Rectangle()
|
||||
.frame(height: 1)
|
||||
.foregroundColor(.gray.opacity(0.2))
|
||||
.foregroundColor(Color("mode").opacity(0.2))
|
||||
}
|
||||
.frame(minHeight: 35)
|
||||
}
|
||||
@@ -497,7 +464,7 @@ struct SimpleSearchStyle: TextFieldStyle {
|
||||
}
|
||||
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.strokeBorder(Color.gray.opacity(0.2), lineWidth: 1)
|
||||
.strokeBorder(Color("mode").opacity(0.2), lineWidth: 1)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
@@ -767,7 +734,7 @@ struct PillPicker: View {
|
||||
|
||||
Text(i == 0 ? "Apps" : (i == 1 ? "Widgets" : "Plugins"))
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(index == i ? (colorScheme == .dark ? .black : Color("AccentColor")) : .gray)
|
||||
.foregroundColor(index == i ? (colorScheme == .dark ? .black : Color("AccentColor")) : Color("mode"))
|
||||
}
|
||||
.padding(5)
|
||||
.frame(height: 25)
|
||||
@@ -826,3 +793,47 @@ struct PearDropView: View {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Used for testing UI bounds
|
||||
extension View {
|
||||
func bounds() -> some View {
|
||||
self.border(Color.red, width: 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
func backgroundView(themeSettings: ThemeSettings, darker: Bool = false, glass: Bool = false) -> some View {
|
||||
if glass {
|
||||
GlassEffect(material: .sidebar, blendingMode: .behindWindow)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
} else {
|
||||
darker ? themeSettings.themeColor.darker(by: 5).edgesIgnoringSafeArea(.all) : themeSettings.themeColor.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct PresetColor: ButtonStyle {
|
||||
var fillColor: Color
|
||||
var label: String
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
HStack(alignment: .center, spacing: 10) {
|
||||
Circle()
|
||||
.frame(width: 20, height: 20)
|
||||
.fixedSize(horizontal: true, vertical: true)
|
||||
.foregroundColor(fillColor)
|
||||
.background(fillColor)
|
||||
.clipShape(Circle())
|
||||
.help(label)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 25)
|
||||
.strokeBorder(Color("mode").opacity(0.8), lineWidth: 1.5))
|
||||
// Text(label)
|
||||
}
|
||||
.onHover { inside in inside ? NSCursor.pointingHand.push() : NSCursor.pop() }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// ThemeManager.swift
|
||||
// Pearcleaner
|
||||
//
|
||||
// Created by Alin Lupascu on 4/12/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
class ThemeSettings: ObservableObject {
|
||||
static let shared = ThemeSettings()
|
||||
private let userDefaults = UserDefaults.standard
|
||||
private let colorKey = "themeColor"
|
||||
|
||||
@Published var themeColor: Color {
|
||||
didSet {
|
||||
saveThemeColor()
|
||||
}
|
||||
}
|
||||
|
||||
// light - Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1)
|
||||
// dark - Color(.sRGB, red: 0.149, green: 0.149, blue: 0.149, opacity: 1)
|
||||
|
||||
init() {
|
||||
// Initialize color from UserDefaults or use a default value
|
||||
if let components = UserDefaults.standard.object(forKey: colorKey) as? [CGFloat], components.count >= 4 {
|
||||
themeColor = Color(.sRGB, red: components[0], green: components[1], blue: components[2], opacity: components[3])
|
||||
} else {
|
||||
themeColor = Color.clear
|
||||
}
|
||||
}
|
||||
|
||||
func saveThemeColor() {
|
||||
let nsColor = NSColor(themeColor)
|
||||
if let components = nsColor.cgColor.components {
|
||||
UserDefaults.standard.set(components, forKey: colorKey)
|
||||
}
|
||||
}
|
||||
|
||||
func resetToDefault(dark: Bool = true) {
|
||||
themeColor = dark ? Color(.sRGB, red: 0.149, green: 0.149, blue: 0.149, opacity: 1) : Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1)
|
||||
saveThemeColor()
|
||||
}
|
||||
}
|
||||
|
||||
extension Color {
|
||||
func darker(by percentage: CGFloat = 30.0) -> Color {
|
||||
var hsb = (hue: CGFloat(0), saturation: CGFloat(0), brightness: CGFloat(0), alpha: CGFloat(0))
|
||||
NSColor(self).getHue(&hsb.hue, saturation: &hsb.saturation, brightness: &hsb.brightness, alpha: &hsb.alpha)
|
||||
return Color(hue: hsb.hue, saturation: hsb.saturation, brightness: max(hsb.brightness - percentage/100, 0), opacity: hsb.alpha)
|
||||
}
|
||||
func lighter(by percentage: CGFloat = 30.0) -> Color {
|
||||
var hsb = (hue: CGFloat(0), saturation: CGFloat(0), brightness: CGFloat(0), alpha: CGFloat(0))
|
||||
NSColor(self).getHue(&hsb.hue, saturation: &hsb.saturation, brightness: &hsb.brightness, alpha: &hsb.alpha)
|
||||
return Color(hue: hsb.hue, saturation: hsb.saturation, brightness: min(hsb.brightness + percentage / 100, 1.0), opacity: hsb.alpha)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user