From f8e7fceb13c62afca9cd69685a6a87c4a6bebc7e Mon Sep 17 00:00:00 2001 From: Tanislav <54242123+tanislavivanov@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:08:01 +0200 Subject: [PATCH 1/2] Add theme color customization and search-in-tab shortcut Introduces a ThemeColorPicker in settings, allowing users to customize the browser's primary and accent colors, with persistence via SettingsStore. Adds a new keyboard shortcut and command for 'Search in Current Tab', updating LauncherView and app state to support searching in the current tab instead of opening a new one. Updates Theme struct to support custom colors, refines version display, and changes the downloads icon. Also lowers the macOS deployment target to 14.0. --- ora/Common/Constants/KeyboardShortcuts.swift | 8 +- ora/Common/Constants/Theme.swift | 31 ++++- ora/Common/Utils/SettingsStore.swift | 13 ++ ora/Modules/Launcher/LauncherView.swift | 25 ++-- .../Sections/GeneralSettingsView.swift | 4 + .../Settings/Sections/ThemeColorPicker.swift | 118 ++++++++++++++++++ ora/Modules/Sidebar/DownloadsWidget.swift | 2 +- ora/OraCommands.swift | 12 +- ora/OraRoot.swift | 13 +- ora/UI/LinkPreview.swift | 2 +- ora/oraApp.swift | 1 + project.yml | 2 +- 12 files changed, 213 insertions(+), 18 deletions(-) create mode 100644 ora/Modules/Settings/Sections/ThemeColorPicker.swift diff --git a/ora/Common/Constants/KeyboardShortcuts.swift b/ora/Common/Constants/KeyboardShortcuts.swift index 2394668f..d4ab58f7 100644 --- a/ora/Common/Constants/KeyboardShortcuts.swift +++ b/ora/Common/Constants/KeyboardShortcuts.swift @@ -203,6 +203,12 @@ enum KeyboardShortcuts { category: "Address", defaultChord: KeyChord(keyEquivalent: .init("l"), modifiers: [.command]) ) + static let searchInCurrentTab = KeyboardShortcutDefinition( + id: "address.searchInCurrentTab", + name: "Search in Current Tab", + category: "Address", + defaultChord: KeyChord(keyEquivalent: .init("g"), modifiers: [.command, .shift]) + ) } // MARK: - Edit @@ -329,7 +335,7 @@ enum KeyboardShortcuts { Window.new, Window.newPrivate, Window.close, Window.fullscreen, // Address - Address.copyURL, Address.focus, + Address.copyURL, Address.focus, Address.searchInCurrentTab, // Edit Edit.find, Edit.findNext, Edit.findPrevious, diff --git a/ora/Common/Constants/Theme.swift b/ora/Common/Constants/Theme.swift index b2224da5..b13eef4f 100644 --- a/ora/Common/Constants/Theme.swift +++ b/ora/Common/Constants/Theme.swift @@ -4,17 +4,32 @@ import SwiftUI // swiftlint:disable identifier_name struct Theme: Equatable { let colorScheme: ColorScheme + let customPrimaryColor: String? + let customAccentColor: String? + + init(colorScheme: ColorScheme, customPrimaryColor: String? = nil, customAccentColor: String? = nil) { + self.colorScheme = colorScheme + self.customPrimaryColor = customPrimaryColor + self.customAccentColor = customAccentColor + } var primary: Color { - Color(hex: "#f3e5d6") + if let hex = customPrimaryColor { + return Color(hex: hex) + } + return Color(hex: "#d6f3ea") } var primaryDark: Color { - Color(hex: "#63411D") + // Use the same color as primary for both light and dark modes + return primary } var accent: Color { - Color(hex: "#FF5F57") + if let hex = customAccentColor { + return Color(hex: hex) + } + return Color(hex: "#575dff") } var background: Color { @@ -140,8 +155,16 @@ extension EnvironmentValues { struct ThemeProvider: ViewModifier { @Environment(\.colorScheme) private var colorScheme + @ObservedObject private var settings = SettingsStore.shared func body(content: Content) -> some View { - content.environment(\.theme, Theme(colorScheme: colorScheme)) + content.environment( + \.theme, + Theme( + colorScheme: colorScheme, + customPrimaryColor: settings.themePrimaryColor, + customAccentColor: settings.themeAccentColor + ) + ) } } diff --git a/ora/Common/Utils/SettingsStore.swift b/ora/Common/Utils/SettingsStore.swift index 821146b6..d9dfb55e 100644 --- a/ora/Common/Utils/SettingsStore.swift +++ b/ora/Common/Utils/SettingsStore.swift @@ -150,6 +150,8 @@ class SettingsStore: ObservableObject { private let tabRemovalTimeoutKey = "settings.tabRemovalTimeout" private let maxRecentTabsKey = "settings.maxRecentTabs" private let autoPiPEnabledKey = "settings.autoPiPEnabled" + private let themePrimaryColorKey = "settings.theme.primaryColor" + private let themeAccentColorKey = "settings.theme.accentColor" // MARK: - Per-Container @@ -217,6 +219,14 @@ class SettingsStore: ObservableObject { didSet { defaults.set(autoPiPEnabled, forKey: autoPiPEnabledKey) } } + @Published var themePrimaryColor: String? { + didSet { defaults.set(themePrimaryColor, forKey: themePrimaryColorKey) } + } + + @Published var themeAccentColor: String? { + didSet { defaults.set(themeAccentColor, forKey: themeAccentColorKey) } + } + init() { autoUpdateEnabled = defaults.bool(forKey: autoUpdateKey) blockThirdPartyTrackers = defaults.bool(forKey: trackingThirdPartyKey) @@ -271,6 +281,9 @@ class SettingsStore: ObservableObject { maxRecentTabs = maxRecentTabsValue == 0 ? 5 : maxRecentTabsValue autoPiPEnabled = defaults.object(forKey: autoPiPEnabledKey) as? Bool ?? true + + themePrimaryColor = defaults.string(forKey: themePrimaryColorKey) + themeAccentColor = defaults.string(forKey: themeAccentColorKey) } // MARK: - Per-container helpers diff --git a/ora/Modules/Launcher/LauncherView.swift b/ora/Modules/Launcher/LauncherView.swift index a574411f..11ee6d17 100644 --- a/ora/Modules/Launcher/LauncherView.swift +++ b/ora/Modules/Launcher/LauncherView.swift @@ -51,15 +51,22 @@ struct LauncherView: View { if let engine = engineToUse, let url = searchEngineService.createSearchURL(for: engine, query: correctInput) { - tabManager - .openTab( - url: url, - historyManager: historyManager, - downloadManager: downloadManager, - isPrivate: privacyMode.isPrivate - ) + if appState.launcherSearchInCurrentTab, let activeTab = tabManager.activeTab { + // Search in current tab + activeTab.loadURL(url.absoluteString) + } else { + // Create new tab (default behavior) + tabManager + .openTab( + url: url, + historyManager: historyManager, + downloadManager: downloadManager, + isPrivate: privacyMode.isPrivate + ) + } } appState.showLauncher = false + appState.launcherSearchInCurrentTab = false } var body: some View { @@ -101,6 +108,10 @@ struct LauncherView: View { } .onChange(of: appState.showLauncher) { _, newValue in isVisible = newValue + if !newValue { + // Reset the flag when launcher is closed + appState.launcherSearchInCurrentTab = false + } } } .frame(maxWidth: .infinity, maxHeight: .infinity) diff --git a/ora/Modules/Settings/Sections/GeneralSettingsView.swift b/ora/Modules/Settings/Sections/GeneralSettingsView.swift index 505e6a94..deaafb82 100644 --- a/ora/Modules/Settings/Sections/GeneralSettingsView.swift +++ b/ora/Modules/Settings/Sections/GeneralSettingsView.swift @@ -44,6 +44,10 @@ struct GeneralSettingsView: View { } AppearanceSelector(selection: $appearanceManager.appearance) + + ThemeColorPicker() + .padding(.vertical, 8) + VStack(alignment: .leading, spacing: 12) { Text("Tab Management") .font(.headline) diff --git a/ora/Modules/Settings/Sections/ThemeColorPicker.swift b/ora/Modules/Settings/Sections/ThemeColorPicker.swift new file mode 100644 index 00000000..ea05cdb2 --- /dev/null +++ b/ora/Modules/Settings/Sections/ThemeColorPicker.swift @@ -0,0 +1,118 @@ +import SwiftUI + +struct ThemeColorPicker: View { + @StateObject private var settings = SettingsStore.shared + @Environment(\.theme) var theme + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text("Theme Colors") + .font(.headline) + + Text("Customize the primary colors used throughout the browser.") + .font(.caption) + .foregroundColor(.secondary) + + VStack(alignment: .leading, spacing: 16) { + // Primary Color (used for both light and dark modes) + ColorPickerRow( + title: "Primary Color", + description: "Used for backgrounds and accents in both light and dark modes", + color: Binding( + get: { + if let hex = settings.themePrimaryColor { + return Color(hex: hex) + } + return Color(hex: "#d6f3ea") + }, + set: { newColor in + settings.themePrimaryColor = newColor.toHex() + } + ), + defaultColor: Color(hex: "#d6f3ea"), + onReset: { + settings.themePrimaryColor = nil + } + ) + + // Accent Color + ColorPickerRow( + title: "Accent Color", + description: "Used for interactive elements and highlights", + color: Binding( + get: { + if let hex = settings.themeAccentColor { + return Color(hex: hex) + } + return Color(hex: "#575dff") + }, + set: { newColor in + settings.themeAccentColor = newColor.toHex() + } + ), + defaultColor: Color(hex: "#575dff"), + onReset: { + settings.themeAccentColor = nil + } + ) + } + } + } +} + +private struct ColorPickerRow: View { + let title: String + let description: String + @Binding var color: Color + let defaultColor: Color + let onReset: () -> Void + + @State private var isUsingCustom = false + + var body: some View { + HStack(spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + Text(title) + .font(.subheadline) + .fontWeight(.medium) + Text(description) + .font(.caption2) + .foregroundColor(.secondary) + } + + Spacer() + + ColorPicker("", selection: $color, supportsOpacity: false) + .labelsHidden() + .frame(width: 50) + + Button { + onReset() + isUsingCustom = false + } label: { + Text("Reset") + .font(.caption) + } + .buttonStyle(.plain) + .foregroundColor(.secondary) + .disabled(!isUsingCustom) + } + .padding(.vertical, 8) + .padding(.horizontal, 12) + .background(Color.secondary.opacity(0.1)) + .cornerRadius(8) + .onChange(of: color) { _, newValue in + // Check if color differs from default (with small tolerance for floating point) + let defaultHex = defaultColor.toHex() ?? "" + let newHex = newValue.toHex() ?? "" + isUsingCustom = defaultHex.lowercased() != newHex.lowercased() + } + .onAppear { + // Check initial state + let defaultHex = defaultColor.toHex() ?? "" + let currentHex = color.toHex() ?? "" + isUsingCustom = defaultHex.lowercased() != currentHex.lowercased() + } + } +} + diff --git a/ora/Modules/Sidebar/DownloadsWidget.swift b/ora/Modules/Sidebar/DownloadsWidget.swift index 7b56ed1e..576e8d32 100644 --- a/ora/Modules/Sidebar/DownloadsWidget.swift +++ b/ora/Modules/Sidebar/DownloadsWidget.swift @@ -24,7 +24,7 @@ struct DownloadsWidget: View { downloadManager.isDownloadsPopoverOpen.toggle() }) { HStack(spacing: 8) { - Image(systemName: "arrow.down") + Image(systemName: "arrow.down.circle") .foregroundColor(downloadButtonColor) .frame(width: 12, height: 12) diff --git a/ora/OraCommands.swift b/ora/OraCommands.swift index 1d70f27c..8bb5856c 100644 --- a/ora/OraCommands.swift +++ b/ora/OraCommands.swift @@ -20,6 +20,14 @@ struct OraCommands: Commands { NotificationCenter.default.post(name: .showLauncher, object: NSApp.keyWindow) }.keyboardShortcut(KeyboardShortcuts.Tabs.new.keyboardShortcut) + Button("Search in Current Tab") { + NotificationCenter.default.post( + name: .showLauncher, + object: NSApp.keyWindow, + userInfo: ["searchInCurrentTab": true] + ) + }.keyboardShortcut(KeyboardShortcuts.Address.searchInCurrentTab.keyboardShortcut) + Divider() ImportDataButton() @@ -183,13 +191,13 @@ struct OraCommands: Commands { private func showAboutWindow() { let alert = NSAlert() - alert.messageText = "Ora Browser" + alert.messageText = "Ora" alert.informativeText = """ Version \(getAppVersion()) Fast, secure, and beautiful browser built for macOS. - © 2025 Ora Browser + © 2025 Ora """ alert.alertStyle = .informational alert.addButton(withTitle: "OK") diff --git a/ora/OraRoot.swift b/ora/OraRoot.swift index 7012099f..8dd886f6 100644 --- a/ora/OraRoot.swift +++ b/ora/OraRoot.swift @@ -121,7 +121,18 @@ struct OraRoot: View { NotificationCenter.default.addObserver(forName: .showLauncher, object: nil, queue: .main) { note in guard note.object as? NSWindow === window ?? NSApp.keyWindow else { return } if tabManager.activeTab != nil { - appState.showLauncher.toggle() + // Check if we should search in current tab + if let searchInCurrentTab = note.userInfo?["searchInCurrentTab"] as? Bool { + appState.launcherSearchInCurrentTab = searchInCurrentTab + } else { + appState.launcherSearchInCurrentTab = false + } + // Only open if not already open, or close if already open + if appState.showLauncher { + appState.showLauncher = false + } else { + appState.showLauncher = true + } } } NotificationCenter.default.addObserver(forName: .closeActiveTab, object: nil, queue: .main) { note in diff --git a/ora/UI/LinkPreview.swift b/ora/UI/LinkPreview.swift index 92cd4367..484f263c 100644 --- a/ora/UI/LinkPreview.swift +++ b/ora/UI/LinkPreview.swift @@ -6,7 +6,7 @@ struct LinkPreview: View { private func getAppVersion() -> String { let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "?" - return "Ora \(version)" + return "Ora v\(version)" } var body: some View { diff --git a/ora/oraApp.swift b/ora/oraApp.swift index 7a2df56e..f2807066 100644 --- a/ora/oraApp.swift +++ b/ora/oraApp.swift @@ -49,6 +49,7 @@ func deleteSwiftDataStore(_ loc: String) { class AppState: ObservableObject { @Published var showLauncher: Bool = false + @Published var launcherSearchInCurrentTab: Bool = false @Published var launcherSearchText: String = "" @Published var showFinderIn: UUID? @Published var isFloatingTabSwitchVisible: Bool = false diff --git a/project.yml b/project.yml index 69c8a0f8..ceb37d1f 100644 --- a/project.yml +++ b/project.yml @@ -11,7 +11,7 @@ targets: ora: type: application platform: "macOS" - deploymentTarget: "15.0" + deploymentTarget: "14.0" sources: - path: ora excludes: From f462b050ee7cae0fedda9425220552c65f158029 Mon Sep 17 00:00:00 2001 From: Tanislav <54242123+tanislavivanov@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:17:48 +0200 Subject: [PATCH 2/2] Update Info.plist and adjust HomeView padding Removed unused keys from Info.plist and added NSAppTransportSecurity to allow arbitrary loads (allows http-only sites to be loaded). Also added extra top padding to the sidebar in HomeView for improved layout. --- ora/Info.plist | 23 +++++------------------ ora/UI/HomeView.swift | 1 + 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/ora/Info.plist b/ora/Info.plist index 3b13bf3a..dfdf8570 100644 --- a/ora/Info.plist +++ b/ora/Info.plist @@ -2,8 +2,6 @@ - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) CFBundleDocumentTypes @@ -43,18 +41,6 @@ - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 CFBundleURLTypes @@ -71,12 +57,13 @@ Owner - CFBundleVersion - 1 - LSApplicationCategoryType - public.app-category.web-browser LSMinimumSystemVersion 13.0 + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSUserActivityTypes NSUserActivityTypeBrowsingWeb diff --git a/ora/UI/HomeView.swift b/ora/UI/HomeView.swift index e7cf2f85..94a5c22c 100644 --- a/ora/UI/HomeView.swift +++ b/ora/UI/HomeView.swift @@ -27,6 +27,7 @@ struct HomeView: View { .zIndex(3) .frame(maxWidth: .infinity, alignment: sidebarManager.sidebarPosition == .primary ? .leading : .trailing) .padding(6) + .padding(.top, 6) .ignoresSafeArea(.all) VStack(alignment: .center, spacing: 16) {