From 4610ae90f266f549633e2c617362c7f4be004598 Mon Sep 17 00:00:00 2001 From: Alin Date: Mon, 8 Jul 2024 11:09:30 -0600 Subject: [PATCH] v3.8.0 --- Pearcleaner.xcodeproj/project.pbxproj | 10 +- Pearcleaner/Logic/Styles.swift | 10 +- Pearcleaner/Logic/ThemeManager.swift | 16 +-- Pearcleaner/PearcleanerApp.swift | 58 ++++---- Pearcleaner/Settings/Interface.swift | 188 ++++++++++++++------------ 5 files changed, 156 insertions(+), 126 deletions(-) diff --git a/Pearcleaner.xcodeproj/project.pbxproj b/Pearcleaner.xcodeproj/project.pbxproj index 3ee059c..ea69efc 100644 --- a/Pearcleaner.xcodeproj/project.pbxproj +++ b/Pearcleaner.xcodeproj/project.pbxproj @@ -34,7 +34,6 @@ C77B90232AF2D616009CC655 /* FilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77B90222AF2D616009CC655 /* FilesView.swift */; }; C78121662BC892A000BE06BD /* FinderOpen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C78121652BC892A000BE06BD /* FinderOpen.swift */; }; C781216B2BC892A000BE06BD /* FinderOpen.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = C78121632BC892A000BE06BD /* FinderOpen.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - C786F1A82C29E599001B3800 /* Builds in Resources */ = {isa = PBXBuildFile; fileRef = C786F1A72C29E599001B3800 /* Builds */; }; C79947962C34A794007279CC /* Tips.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79947952C34A794007279CC /* Tips.swift */; }; C79D05202BC99EB40083F976 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79D051F2BC99EB40083F976 /* ThemeManager.swift */; }; C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */ = {isa = PBXBuildFile; fileRef = C7A27E802AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist */; }; @@ -447,7 +446,6 @@ buildActionMask = 2147483647; files = ( C77B90082AF18E2F009CC655 /* Assets.xcassets in Resources */, - C786F1A82C29E599001B3800 /* Builds in Resources */, C7A27E812AFD7C4600166168 /* com.alienator88.PearcleanerSentinel.plist in Resources */, C7DC1FEE2B9F7D3D009AC317 /* features.json in Resources */, ); @@ -588,8 +586,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_BUILD = 53; - APP_VERSION = 3.7.8; + APP_BUILD = 54; + APP_VERSION = 3.8.0; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -658,8 +656,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - APP_BUILD = 53; - APP_VERSION = 3.7.8; + APP_BUILD = 54; + APP_VERSION = 3.8.0; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/Pearcleaner/Logic/Styles.swift b/Pearcleaner/Logic/Styles.swift index 2846f56..0dfcc5b 100644 --- a/Pearcleaner/Logic/Styles.swift +++ b/Pearcleaner/Logic/Styles.swift @@ -962,10 +962,10 @@ struct PickerModifier: ViewModifier { content .buttonStyle(.borderless) .padding(4) - .background { - backgroundView(themeSettings: themeSettings, darker: isHovered) - .clipShape(RoundedRectangle(cornerRadius: 10)) - } +// .background { +// backgroundView(themeSettings: themeSettings, darker: isHovered) +// .clipShape(RoundedRectangle(cornerRadius: 10)) +// } .onHover { hovering in isHovered = hovering } @@ -995,7 +995,7 @@ struct PresetColor: ButtonStyle { .help(label) .overlay( RoundedRectangle(cornerRadius: 25) - .strokeBorder(Color("mode").opacity(0.8), lineWidth: 1.5)) + .strokeBorder(Color("mode").opacity(0.8), lineWidth: 1)) // Text(label) } .onHover { inside in inside ? NSCursor.pointingHand.push() : NSCursor.pop() } diff --git a/Pearcleaner/Logic/ThemeManager.swift b/Pearcleaner/Logic/ThemeManager.swift index e0ab8bc..2e12ef7 100644 --- a/Pearcleaner/Logic/ThemeManager.swift +++ b/Pearcleaner/Logic/ThemeManager.swift @@ -28,14 +28,14 @@ class ThemeSettings: ObservableObject { themeColor = userDefaults.color(forKey: colorKey) ?? .clear } - func setupInitialColor() { - if userDefaults.color(forKey: colorKey) == nil { - // Only set the initial color if it has not been set by the user. - let darkMode = isDarkMode() // Safe to call here after the application is fully set up. - themeColor = darkMode ? Color(.sRGB, red: 0.188143, green: 0.208556, blue: 0.262679, opacity: 1) : - Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1) - saveThemeColor() - } + func setupInitialColor(forcedDarkMode: Bool? = nil) { + // Determine dark mode either from forced setting or system setting + let darkMode = forcedDarkMode ?? isDarkMode() + + // Set the theme color based on the dark mode status + themeColor = darkMode ? Color(.sRGB, red: 0.188143, green: 0.208556, blue: 0.262679, opacity: 1) : + Color(.sRGB, red: 1.0, green: 1.0, blue: 1.0, opacity: 1) + saveThemeColor() } private func loadThemeColor() { diff --git a/Pearcleaner/PearcleanerApp.swift b/Pearcleaner/PearcleanerApp.swift index dbdc635..12381a9 100644 --- a/Pearcleaner/PearcleanerApp.swift +++ b/Pearcleaner/PearcleanerApp.swift @@ -177,10 +177,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { // UserDefaults.standard.register(defaults: ["NSQuitAlwaysKeepsWindows" : false]) findAndSetWindowFrame(named: ["Pearcleaner"], windowSettings: windowSettings) - -// if UserDefaults.standard.object(forKey: "themeColor") == nil { -// self.appearanceChanged() -// } self.appearanceCheck() @@ -191,11 +187,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { // Start observing the appearance change observer = DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil, queue: OperationQueue.main) { [weak self] _ in -// let themeMode = UserDefaults.standard.string(forKey: "settings.general.selectedTheme") -// if themeMode == "Auto" { -// self?.appearanceCheck() -// } - self?.appearanceCheck(reset: true) + let themeMode = UserDefaults.standard.string(forKey: "settings.general.selectedTheme") + let themesEnabled = UserDefaults.standard.bool(forKey: "settings.interface.themesEnabled") + if themeMode == "Auto" && !themesEnabled { + self?.appearanceCheck(reset: true) + } } } @@ -203,30 +199,46 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { func appearanceCheck(reset: Bool = false) { - // Setup initial color - ThemeSettings.shared.setupInitialColor() + let themesEnabled = UserDefaults.standard.bool(forKey: "settings.interface.themesEnabled") + let themeMode = UserDefaults.standard.string(forKey: "settings.general.selectedTheme") - // Get the current theme color - let themeColor = ThemeSettings.shared.themeColor - - // Determine if the color is light or dark - if let isLightColor = themeColor.isLight() { - let shouldUseDarkMode = !isLightColor // Use dark mode if color is dark - UserDefaults.standard.set(shouldUseDarkMode ? DisplayMode.dark.rawValue : DisplayMode.light.rawValue, forKey: "displayMode") + if themesEnabled { + // Custom theming is enabled; user's manual settings take priority. } else { - // Default to system preference if unable to determine color brightness - let dark = isDarkMode() - UserDefaults.standard.set(dark ? DisplayMode.dark.rawValue : DisplayMode.light.rawValue, forKey: "displayMode") + switch themeMode { + case "Auto": + // Auto mode: adjust theme based on system appearance. + ThemeSettings.shared.setupInitialColor() + updateDisplayModeBasedOnThemeColor() + case "Light", "Dark": + // Specific mode set: ensure theme color matches the display mode. + ThemeSettings.shared.setupInitialColor(forcedDarkMode: themeMode == "Dark") + UserDefaults.standard.set(themeMode == "Dark" ? DisplayMode.dark.rawValue : DisplayMode.light.rawValue, forKey: "displayMode") + default: + // No specific handling needed or unrecognized mode; fall back to default behavior. + printOS("No specific theme mode set or unrecognized value.") + } } - // Reset theme when OS changes appearance - if reset { + if reset && themeMode == "Auto" { let dark = isDarkMode() ThemeSettings.shared.resetToDefault(dark: dark) } } + func updateDisplayModeBasedOnThemeColor() { + let themeColor = ThemeSettings.shared.themeColor + if let isLightColor = themeColor.isLight() { + let shouldUseDarkMode = !isLightColor + UserDefaults.standard.set(shouldUseDarkMode ? DisplayMode.dark.rawValue : DisplayMode.light.rawValue, forKey: "displayMode") + } else { + // Default to system preference if brightness cannot be determined. + let dark = isDarkMode() + UserDefaults.standard.set(dark ? DisplayMode.dark.rawValue : DisplayMode.light.rawValue, forKey: "displayMode") + } + } + func applicationWillTerminate(_ notification: Notification) { // Stop observing the appearance change if let observer = observer { diff --git a/Pearcleaner/Settings/Interface.swift b/Pearcleaner/Settings/Interface.swift index 298a84f..bd16002 100644 --- a/Pearcleaner/Settings/Interface.swift +++ b/Pearcleaner/Settings/Interface.swift @@ -23,9 +23,9 @@ struct InterfaceSettingsTab: View { @AppStorage("settings.general.selectedTab") private var selectedTab: CurrentTabView = .general @AppStorage("settings.general.glass") private var glass: Bool = false @AppStorage("settings.general.dark") var isDark: Bool = true + @AppStorage("settings.interface.themesEnabled") var themesEnabled: Bool = false @AppStorage("settings.general.popover") private var popoverStay: Bool = true @AppStorage("settings.general.miniview") private var miniView: Bool = true -// @AppStorage("settings.general.animateLogo") private var animateLogo: Bool = true @AppStorage("settings.general.selectedTheme") var selectedTheme: String = "Auto" @AppStorage("settings.interface.selectedMenubarIcon") var selectedMenubarIcon: String = "pear-4" @State private var isLaunchAtLoginEnabled: Bool = false @@ -69,99 +69,109 @@ struct InterfaceSettingsTab: View { .padding(.leading) -// HStack(spacing: 0) { -// Image(systemName: animateLogo ? "play.fill" : "pause") -// .resizable() -// .scaledToFit() -// .frame(width: 20, height: 20) -// .padding(.trailing) -// .foregroundStyle(Color("mode").opacity(0.5)) -// VStack(alignment: .leading, spacing: 5) { -// Text("\(animateLogo ? "Logo animation enabled" : "Logo animation disabled")") -// .font(.callout) -// .foregroundStyle(Color("mode").opacity(0.5)) -// -// } -// if !isMacOS14OrHigher { -// Text("(macOS 14+)") -// .font(.footnote) -// .foregroundStyle(Color("mode").opacity(0.3)) -// .padding(.leading, 5) -// } -//// InfoButton(text: "The logo animation is only available in macOS 14 or higher") -// Spacer() -// Toggle(isOn: $animateLogo, label: { -// }) -// .toggleStyle(.switch) -// .disabled(!isMacOS14OrHigher) -// } -// .padding(5) -// .padding(.leading) - - - HStack(spacing: 0) { - Image(systemName: "paintbrush") + Image(systemName: themesEnabled ? "paintpalette.fill" : "paintpalette") .resizable() .scaledToFit() .frame(width: 20, height: 20) .padding(.trailing) .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { - Text("Application base color") + Text("\(themesEnabled ? "Custom theming is enabled" : "Custom theming is disabled")") .font(.callout) .foregroundStyle(Color("mode").opacity(0.5)) } - InfoButton(text: "When using a custom color, you might need to change the application color mode below to Dark or Light so text is readable") Spacer() - - - HStack(spacing: 10) { - Button("") { - themeSettings.setPreset(preset: "pearcleaner", colorScheme: displayMode) + Toggle(isOn: $themesEnabled, label: { + }) + .toggleStyle(.switch) + .onChange(of: themesEnabled) { newVal in + themeSettings.resetToDefault(dark: isDarkMode()) + if isDarkMode() { + displayMode.colorScheme = .dark + } else { + displayMode.colorScheme = .light } - .buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "pearcleaner", colorScheme: displayMode), label: "Pearcleaner")) - - Button("") { - themeSettings.setPreset(preset: "dracula", colorScheme: displayMode) + if newVal { + selectedTheme = isDarkMode() ? "Dark" : "Light" + } else { + selectedTheme = "Auto" } - .buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "dracula", colorScheme: displayMode), label: "Dracula")) - - Button("") { - themeSettings.setPreset(preset: "solarized", colorScheme: displayMode) - themeSettings.saveThemeColor() - } - .buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "solarized", colorScheme: displayMode), label: "Solarized")) - - Button("") { - themeSettings.setPreset(preset: "macOS", colorScheme: displayMode) - themeSettings.saveThemeColor() - } - .buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "macOS", colorScheme: displayMode), label: "macOS")) } - - - - - Spacer() - - ColorPicker("", selection: $themeSettings.themeColor, supportsOpacity: false) - .onChange(of: themeSettings.themeColor) { newValue in - themeSettings.saveThemeColor() - } - .padding(.horizontal, 5) - - Button("") { - themeSettings.resetToDefault(dark: colorScheme == .dark) - } - .buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Reset color to default")) - } .padding(5) .padding(.leading) + /// Show theme color selector if theming is enabled + if themesEnabled { + HStack(spacing: 0) { + Image(systemName: "paintbrush") + .resizable() + .scaledToFit() + .frame(width: 20, height: 20) + .padding(.trailing) + .foregroundStyle(Color("mode").opacity(0.5)) + VStack(alignment: .leading, spacing: 5) { + Text("Application theme color") + .font(.callout) + .foregroundStyle(Color("mode").opacity(0.5)) + } +// InfoButton(text: "When using a custom color, you might need to change the application color mode below to Dark or Light so text is readable") + Spacer() + + + HStack(spacing: 10) { + Button("") { + themeSettings.setPreset(preset: "pearcleaner", colorScheme: displayMode) + } + .buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "pearcleaner", colorScheme: displayMode), label: "Pearcleaner")) + + Button("") { + themeSettings.setPreset(preset: "dracula", colorScheme: displayMode) + } + .buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "dracula", colorScheme: displayMode), label: "Dracula")) + + Button("") { + themeSettings.setPreset(preset: "solarized", colorScheme: displayMode) + } + .buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "solarized", colorScheme: displayMode), label: "Solarized")) + + Button("") { + themeSettings.setPreset(preset: "macOS", colorScheme: displayMode) + } + .buttonStyle(PresetColor(fillColor: themeSettings.getColorForPreset(preset: "macOS", colorScheme: displayMode), label: "macOS")) + } + + + + + Spacer() + + ColorPicker("", selection: $themeSettings.themeColor, supportsOpacity: false) + .onChange(of: themeSettings.themeColor) { newValue in + themeSettings.saveThemeColor() + } + .padding(.horizontal, 5) + + Button("") { + themeSettings.resetToDefault(dark: isDarkMode()) + if isDarkMode() { + displayMode.colorScheme = .dark + } else { + displayMode.colorScheme = .light + } + selectedTheme = isDarkMode() ? "Dark" : "Light" + } + .buttonStyle(SimpleButtonStyle(icon: "arrow.uturn.left.circle", help: "Reset color to default")) + + } + .padding(5) + .padding(.leading) + } + + HStack(spacing: 0) { Image(systemName: { @@ -180,18 +190,20 @@ struct InterfaceSettingsTab: View { .padding(.trailing) .foregroundStyle(Color("mode").opacity(0.5)) VStack(alignment: .leading, spacing: 5) { - Text("Application color mode") + Text(themesEnabled ? "Application font color" : "Application color mode") .font(.callout) .foregroundStyle(Color("mode").opacity(0.5)) } - InfoButton(text: "This changes the text color to match the OS. It will also reset the base color to defaults when swapping between these.") + InfoButton(text: "When custom theming is disabled, you can set the application color mode to follow the operating system using Auto. Or manually set it to Light or Dark. When custom theming is enabled, this selector acts mainly as a font color picker between light or dark to match your custom theme color.") Spacer() Picker("", selection: $selectedTheme) { - Text("Auto") - .tag("Auto") - Text("Dark") + if !themesEnabled { + Text("Auto") + .tag("Auto") + } + Text(themesEnabled ? "Light" : "Dark") .tag("Dark") - Text("Light") + Text(themesEnabled ? "Dark" : "Light") .tag("Light") } .pickerStyle(themeSettings: themeSettings) @@ -203,7 +215,9 @@ struct InterfaceSettingsTab: View { } else { displayMode.colorScheme = .light } - themeSettings.resetToDefault(dark: isDarkMode()) + if !themesEnabled { + themeSettings.resetToDefault(dark: isDarkMode()) + } // Refresh foreground colors DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.selectedTab = .interface @@ -213,7 +227,9 @@ struct InterfaceSettingsTab: View { } case "Dark": displayMode.colorScheme = .dark - themeSettings.resetToDefault(dark: true) + if !themesEnabled { + themeSettings.resetToDefault(dark: true) + } DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.selectedTab = .interface } @@ -222,7 +238,9 @@ struct InterfaceSettingsTab: View { } case "Light": displayMode.colorScheme = .light - themeSettings.resetToDefault(dark: false) + if !themesEnabled { + themeSettings.resetToDefault(dark: false) + } DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.selectedTab = .interface } @@ -515,6 +533,8 @@ struct InterfaceSettingsTab: View { .onChange(of: selectedMenubarIcon) { newValue in MenuBarExtraManager.shared.swapMenuBarIcon(icon: newValue) } + .pickerStyle(themeSettings: themeSettings) + } .padding(5) @@ -525,7 +545,7 @@ struct InterfaceSettingsTab: View { } .padding(20) - .frame(width: 500, height: 580) + .frame(width: 500, height: themesEnabled ? 600 : 580) }