From f105100cb5c912b848e72b45974e619418d04d91 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Fri, 17 Jan 2025 18:16:20 +0600 Subject: [PATCH] Switch to tab implementation (#3660) Task/Issue URL: https://app.asana.com/0/1202406491309510/1208236460435649/f Tech Design URL: https://app.asana.com/0/72649045549333/1208270496320813/f --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Arrow-Right-12.pdf | Bin 0 -> 4229 bytes .../Arrow-Right-12.imageset/Contents.json | 15 ++ .../OpenTabSuggestion.imageset/Contents.json | 15 ++ .../Window-Tabbed-16D 1.pdf | Bin 0 -> 10940 bytes DuckDuckGo/Common/Localizables/UserText.swift | 5 + DuckDuckGo/Common/View/AppKit/ColorView.swift | 14 +- .../Model/HomePageAddressBarModel.swift | 2 +- DuckDuckGo/Localizable.xcstrings | 120 ++++++++++ .../MainWindow/MainViewController.swift | 1 - DuckDuckGo/Menus/MainMenuActions.swift | 3 +- .../AddressBarButtonsViewController.swift | 6 +- .../View/AddressBarTextField.swift | 59 +++-- .../View/AddressBarViewController.swift | 75 ++++++- .../View/NavigationBar.storyboard | 95 ++++++-- .../View/NavigationBarViewController.swift | 12 +- .../AppStateChangedPublisher.swift | 1 + DuckDuckGo/Statistics/GeneralPixel.swift | 6 +- .../Model/SuggestionContainer.swift | 93 ++++++-- .../Suggestions/View/Suggestion.storyboard | 62 +++++- .../View/SuggestionTableCellView.swift | 110 ++++++++-- .../View/SuggestionViewController.swift | 12 +- .../ViewModel/SuggestionViewModel.swift | 19 +- DuckDuckGo/Tab/Model/TabContent.swift | 7 +- .../Tab/View/BrowserTabViewController.swift | 1 + DuckDuckGo/Tab/ViewModel/TabViewModel.swift | 1 + DuckDuckGo/TabBar/View/TabBarViewItem.swift | 4 +- .../ViewModel/TabCollectionViewModel.swift | 10 + .../View/WindowControllersManager.swift | 90 ++++++-- .../Tab/SearchNonexistentDomainTests.swift | 6 +- .../AppKitExtensions/NSColorExtension.swift | 25 +++ .../DataBrokerProtection/Package.swift | 2 +- LocalPackages/FeatureFlags/Package.swift | 2 +- .../Sources/FeatureFlags/FeatureFlag.swift | 5 + .../NetworkProtectionMac/Package.swift | 2 +- LocalPackages/NewTabPage/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- LocalPackages/WebKitExtensions/Package.swift | 2 +- .../DBP/FreemiumDBPPresenterTests.swift | 1 + .../RecentlyClosedCoordinatorTests.swift | 2 + .../Model/SuggestionContainerTests.swift | 206 +++++++++++++++++- .../Model/SuggestionLoadingMock.swift | 2 + .../SuggestionContainerViewModelTests.swift | 34 ++- .../ViewModel/SuggestionViewModelTests.swift | 44 ++++ 45 files changed, 1019 insertions(+), 162 deletions(-) create mode 100644 DuckDuckGo/Assets.xcassets/Images/Arrow-Right-12.imageset/Arrow-Right-12.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/Arrow-Right-12.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/OpenTabSuggestion.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/OpenTabSuggestion.imageset/Window-Tabbed-16D 1.pdf diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5e107a76ba..84672f2d10 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -15366,7 +15366,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 224.7.1; + version = 224.7.2; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6bd4e7c6a0..598affb3be 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "5acb297db5c0edabe13c79efa33b1d0e545d6bff", - "version" : "224.7.1" + "revision" : "b3a8ea5ef9821203fe88a12e3f15ad48b7278a6a", + "version" : "224.7.2" } }, { diff --git a/DuckDuckGo/Assets.xcassets/Images/Arrow-Right-12.imageset/Arrow-Right-12.pdf b/DuckDuckGo/Assets.xcassets/Images/Arrow-Right-12.imageset/Arrow-Right-12.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2879577530dc4be2a73ce0d48c332398520fd885 GIT binary patch literal 4229 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 literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index c1ff976972..15e9c5cdd9 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -253,6 +253,9 @@ struct UserText { static let searchDuckDuckGoSuffix = NSLocalizedString("address.bar.search.suffix", value: "Search DuckDuckGo", comment: "Suffix of searched terms in address bar. Example: best watching machine . Search DuckDuckGo") + static let duckDuckGoSearchSuffix = NSLocalizedString("address.bar.search.open.tab.suffix", + value: "DuckDuckGo Search", + comment: "Suffix of DuckDuckGo Search open tab suggestion. Example: cats – DuckDuckGo Search") static let addressBarVisitSuffix = NSLocalizedString("address.bar.visit.suffix", value: "Visit", comment: "Address bar suffix of possibly visited website. Example: spreadprivacy.com . Visit spreadprivacy.com") @@ -1435,4 +1438,6 @@ struct UserText { static let homePagePromotionFreemiumDBPPostScanEngagementButtonTitle = "View Results" static let removeSuggestionTooltip = NSLocalizedString("remove.suggestion.tooltip", value: "Remove from browsing history", comment: "Tooltip for the button which removes the history entry from the history") + + static let switchToTab = NSLocalizedString("switch.to.tab", value: "Switch to Tab", comment: "Suggestion to switch to an open tab button title") } diff --git a/DuckDuckGo/Common/View/AppKit/ColorView.swift b/DuckDuckGo/Common/View/AppKit/ColorView.swift index dd48e068e1..340c3b9d74 100644 --- a/DuckDuckGo/Common/View/AppKit/ColorView.swift +++ b/DuckDuckGo/Common/View/AppKit/ColorView.swift @@ -41,7 +41,9 @@ internal class ColorView: NSView { @IBInspectable var backgroundColor: NSColor? = NSColor.clear { didSet { - layer?.backgroundColor = backgroundColor?.cgColor + NSAppearance.withAppAppearance { + layer?.backgroundColor = backgroundColor?.cgColor + } } } @@ -54,7 +56,9 @@ internal class ColorView: NSView { @IBInspectable var borderColor: NSColor? { didSet { - layer!.borderColor = borderColor?.cgColor + NSAppearance.withAppAppearance { + layer!.borderColor = borderColor?.cgColor + } } } @@ -72,8 +76,10 @@ internal class ColorView: NSView { override func updateLayer() { super.updateLayer() - layer?.backgroundColor = backgroundColor?.cgColor - layer?.borderColor = borderColor?.cgColor + NSAppearance.withAppAppearance { + layer?.backgroundColor = backgroundColor?.cgColor + layer?.borderColor = borderColor?.cgColor + } } // MARK: - Click Event Interception diff --git a/DuckDuckGo/HomePage/Model/HomePageAddressBarModel.swift b/DuckDuckGo/HomePage/Model/HomePageAddressBarModel.swift index 550990296d..e55b659ece 100644 --- a/DuckDuckGo/HomePage/Model/HomePageAddressBarModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageAddressBarModel.swift @@ -144,7 +144,7 @@ extension HomePage.Models { return AddressBarViewController( coder: coder, tabCollectionViewModel: tabCollectionViewModel, - isBurner: tabCollectionViewModel.isBurner, + burnerMode: tabCollectionViewModel.burnerMode, popovers: nil, isSearchBox: true ) diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index b90ea9b9b0..fddda99763 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -1229,6 +1229,66 @@ } } }, + "address.bar.search.open.tab.suffix" : { + "comment" : "Suffix of DuckDuckGo Search open tab suggestion. Example: cats – DuckDuckGo Search", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "DuckDuckGo Search" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recherche DuckDuckGo" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo-Suche" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wyszukiwanie DuckDuckGo" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Búsqueda de DuckDuckGo" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поиск DuckDuckGo" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zoeken met DuckDuckGo" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pesquisa DuckDuckGo" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ricerca DuckDuckGo" + } + } + } + }, "address.bar.search.suffix" : { "comment" : "Suffix of searched terms in address bar. Example: best watching machine . Search DuckDuckGo", "extractionState" : "extracted_with_value", @@ -63955,6 +64015,66 @@ } } }, + "switch.to.tab" : { + "comment" : "Suggestion to switch to an open tab button title", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Switch to Tab" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passer à l'onglet" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zu Tab wechseln" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Przełącz na kartę" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cambiar a Pestaña" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Перейти на вкладку" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Overschakelen naar tabblad" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mudar para separador" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Passa alla scheda" + } + } + } + }, "sync.promo.bookmarks.message" : { "comment" : "Message for the Sync Promotion banner when user has bookmarks that can be synced", "extractionState" : "extracted_with_value", diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index ffc06c00aa..95d0608c05 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -119,7 +119,6 @@ final class MainViewController: NSViewController { }() navigationBarViewController = NavigationBarViewController.create(tabCollectionViewModel: tabCollectionViewModel, - isBurner: isBurner, networkProtectionPopoverManager: networkProtectionPopoverManager, networkProtectionStatusReporter: networkProtectionStatusReporter, autofillPopoverPresenter: autofillPopoverPresenter, diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index b0bc789066..77e928f30b 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -1104,7 +1104,8 @@ extension MainViewController: NSMenuItemValidation { // Pin Tab case #selector(MainViewController.pinOrUnpinTab(_:)): guard getActiveTabAndIndex()?.tab.isUrl == true, - tabCollectionViewModel.pinnedTabsManager != nil + tabCollectionViewModel.pinnedTabsManager != nil, + !isBurner else { return false } diff --git a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift index 663ffa6019..2966804cc8 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift @@ -747,10 +747,12 @@ final class AddressBarButtonsViewController: NSViewController { imageButton.image = .web case .browsing: imageButton.image = tabViewModel.favicon - case .editing(isUrl: true): + case .editing(.url): imageButton.image = .web - case .editing(isUrl: false): + case .editing(.text): imageButton.image = .search + case .editing(.openTabSuggestion): + imageButton.image = .openTabSuggestion default: imageButton.image = nil } diff --git a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift index 65c5207f49..3072a32683 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift @@ -361,6 +361,8 @@ final class AddressBarTextField: NSTextField { } case .historyEntry: return .autocompleteClickHistory(from: source, cohort: ntpExperimentCohort, onboardingCohort: ntpExperiment.onboardingCohort) + case .openTab: + return .autocompleteClickOpenTab(from: source, cohort: ntpExperimentCohort, onboardingCohort: ntpExperiment.onboardingCohort) default: return nil } @@ -370,7 +372,13 @@ final class AddressBarTextField: NSTextField { PixelKit.fire(autocompletePixel) } - if NSApp.isCommandPressed { + if case .internalPage(title: let title, url: let url) = suggestion, + url == .bookmarks || url.isSettingsURL { + // when choosing an internal page suggestion prefer already open tab + switchTo(OpenTab(title: title, url: url)) + } else if case .openTab(let title, url: let url) = suggestion { + switchTo(OpenTab(title: title, url: url)) + } else if NSApp.isCommandPressed { openNew(NSApp.isOptionPressed ? .window : .tab, selected: NSApp.isShiftPressed, suggestion: suggestion) } else { hideSuggestionWindow() @@ -485,6 +493,12 @@ final class AddressBarTextField: NSTextField { } } + private func switchTo(_ tab: OpenTab) { + // reset value so it‘s not restored next time we come back to the tab + value = .text("", userTyped: false) + WindowControllersManager.shared.show(url: tab.url, source: .switchToOpenTab, newTab: true /* in case not found */) + } + private func makeUrl(suggestion: Suggestion?, stringValueWithoutSuffix: String, completion: @escaping (URL?, String, Bool) -> Void) { let finalUrl: URL? let userEnteredValue: String @@ -889,8 +903,7 @@ extension AddressBarTextField { case .bookmark(title: _, url: let url, isFavorite: _, allowedInTopHits: _), .historyEntry(title: _, url: let url, allowedInTopHits: _), - .internalPage(title: _, url: let url), - .openTab(title: _, url: let url): + .internalPage(title: _, url: let url): if let title = suggestionViewModel.title, !title.isEmpty, suggestionViewModel.autocompletionString != title { @@ -900,7 +913,8 @@ extension AddressBarTextField { } else { self = .url(url) } - + case .openTab(title: _, url: let url): + self = .openTab(url) case .unknown: self = Suffix.search } @@ -910,6 +924,7 @@ extension AddressBarTextField { case visit(host: String) case url(URL) case title(String) + case openTab(URL) func toAttributedString(size: CGFloat, isBurner: Bool) -> NSAttributedString { let suffixColor = isBurner ? NSColor.burnerAccent : NSColor.addressBarSuffix @@ -921,6 +936,8 @@ extension AddressBarTextField { } static let searchSuffix = " – \(UserText.searchDuckDuckGoSuffix)" + static let searchOpenTabSuffix = " – \(UserText.duckDuckGoSearchSuffix)" + static let internalPageOpenTabSuffix = " – \(UserText.duckDuckGo)" static let visitSuffix = " – \(UserText.addressBarVisitSuffix)" var string: String { @@ -929,14 +946,16 @@ extension AddressBarTextField { return Self.searchSuffix case .visit(host: let host): return "\(Self.visitSuffix) \(host)" - case .url(let url): - if url.isDuckDuckGoSearch { - return Self.searchSuffix - } else { - return " – " + url.toString(decodePunycode: false, - dropScheme: true, - dropTrailingSlash: false) - } + case .openTab(let url) where url.isDuckDuckGoSearch: + return Self.searchOpenTabSuffix + case .openTab(let url) where url.isDuckURLScheme: + return Self.internalPageOpenTabSuffix + case .url(let url) where url.isDuckDuckGoSearch: + return Self.searchSuffix + case .url(let url), .openTab(let url): + return " – " + url.toString(decodePunycode: false, + dropScheme: true, + dropTrailingSlash: false) case .title(let title): return " – " + title } @@ -1036,14 +1055,14 @@ extension AddressBarTextField: NSTextFieldDelegate { return true case #selector(NSResponder.deleteBackward(_:)), - #selector(NSResponder.deleteForward(_:)), - #selector(NSResponder.deleteToMark(_:)), - #selector(NSResponder.deleteWordForward(_:)), - #selector(NSResponder.deleteWordBackward(_:)), - #selector(NSResponder.deleteToEndOfLine(_:)), - #selector(NSResponder.deleteToEndOfParagraph(_:)), - #selector(NSResponder.deleteToBeginningOfLine(_:)), - #selector(NSResponder.deleteBackwardByDecomposingPreviousCharacter(_:)): + #selector(NSResponder.deleteForward(_:)), + #selector(NSResponder.deleteToMark(_:)), + #selector(NSResponder.deleteWordForward(_:)), + #selector(NSResponder.deleteWordBackward(_:)), + #selector(NSResponder.deleteToEndOfLine(_:)), + #selector(NSResponder.deleteToEndOfParagraph(_:)), + #selector(NSResponder.deleteToBeginningOfLine(_:)), + #selector(NSResponder.deleteBackwardByDecomposingPreviousCharacter(_:)): suggestionContainerViewModel?.clearSelection() return false diff --git a/DuckDuckGo/NavigationBar/View/AddressBarViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarViewController.swift index 2d98d5f052..b7686702eb 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarViewController.swift @@ -33,6 +33,9 @@ final class AddressBarViewController: NSViewController, ObservableObject { @IBOutlet var passiveTextFieldMinXConstraint: NSLayoutConstraint! @IBOutlet var activeTextFieldMinXConstraint: NSLayoutConstraint! @IBOutlet var buttonsContainerView: NSView! + @IBOutlet var switchToTabBox: ColorView! + @IBOutlet var switchToTabLabel: NSTextField! + @IBOutlet var switchToTabBoxMinXConstraint: NSLayoutConstraint! private static let defaultActiveTextFieldMinX: CGFloat = 40 private let popovers: NavigationBarPopovers? @@ -46,7 +49,13 @@ final class AddressBarViewController: NSViewController, ObservableObject { let isSearchBox: Bool enum Mode: Equatable { - case editing(isUrl: Bool) + enum EditingMode { + case text + case url + case openTabSuggestion + } + + case editing(EditingMode) case browsing var isEditing: Bool { @@ -54,7 +63,11 @@ final class AddressBarViewController: NSViewController, ObservableObject { } } - private var mode: Mode = .editing(isUrl: false) { + private enum Constants { + static let switchToTabMinXPadding: CGFloat = 34 + } + + private var mode: Mode = .editing(.text) { didSet { addressBarButtonsViewController?.controllerMode = mode } @@ -63,6 +76,7 @@ final class AddressBarViewController: NSViewController, ObservableObject { private var isFirstResponder = false { didSet { updateView() + updateSwitchToTabBoxAppearance() self.addressBarButtonsViewController?.isTextFieldEditorFirstResponder = isFirstResponder self.clickPoint = nil // reset click point if the address bar activated during click } @@ -88,7 +102,7 @@ final class AddressBarViewController: NSViewController, ObservableObject { init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, - isBurner: Bool, + burnerMode: BurnerMode, popovers: NavigationBarPopovers?, isSearchBox: Bool = false, onboardingPixelReporter: OnboardingAddressBarReporting = OnboardingPixelReporter()) { @@ -96,9 +110,9 @@ final class AddressBarViewController: NSViewController, ObservableObject { self.popovers = popovers self.suggestionContainerViewModel = SuggestionContainerViewModel( isHomePage: tabViewModel?.tab.content == .newtab, - isBurner: isBurner, - suggestionContainer: SuggestionContainer()) - self.isBurner = isBurner + isBurner: burnerMode.isBurner, + suggestionContainer: SuggestionContainer(burnerMode: burnerMode)) + self.isBurner = burnerMode.isBurner self.onboardingPixelReporter = onboardingPixelReporter self.isSearchBox = isSearchBox @@ -115,6 +129,9 @@ final class AddressBarViewController: NSViewController, ObservableObject { addressBarTextField.placeholderString = UserText.addressBarPlaceholder addressBarTextField.setAccessibilityIdentifier("AddressBarViewController.addressBarTextField") + switchToTabBox.isHidden = true + switchToTabLabel.attributedStringValue = SuggestionTableCellView.switchToTabAttributedString + updateView() // only activate active text field leading constraint on its appearance to avoid constraint conflicts activeTextFieldMinXConstraint.isActive = false @@ -154,6 +171,13 @@ final class AddressBarViewController: NSViewController, ObservableObject { selector: #selector(textFieldFirstReponderNotification(_:)), name: .firstResponder, object: nil) + NSApp.publisher(for: \.effectiveAppearance) + .dropFirst() + .sink { [weak self] _ in + self?.refreshAddressBarAppearance(nil) + } + .store(in: &cancellables) + addMouseMonitors() } subscribeToSelectedTabViewModel() @@ -224,6 +248,7 @@ final class AddressBarViewController: NSViewController, ObservableObject { updateMode(value: value) addressBarButtonsViewController?.textFieldValue = value updateView() + updateSwitchToTabBoxAppearance() } .store(in: &cancellables) } @@ -360,6 +385,25 @@ final class AddressBarViewController: NSViewController, ObservableObject { addressBarTextField.placeholderString = tabViewModel?.tab.content == .newtab ? UserText.addressBarPlaceholder : "" } + private func updateSwitchToTabBoxAppearance() { + guard case .editing(.openTabSuggestion) = mode, + addressBarTextField.isVisible, let editor = addressBarTextField.editor else { + switchToTabBox.isHidden = true + switchToTabBox.alphaValue = 0 + return + } + + if !switchToTabBox.isVisible { + switchToTabBox.isShown = true + switchToTabBox.alphaValue = 0 + } + // update box position on the next pass after text editor layout is updated + DispatchQueue.main.async { + self.switchToTabBox.alphaValue = 1 + self.switchToTabBoxMinXConstraint.constant = editor.textSize.width + Constants.switchToTabMinXPadding + } + } + private func updateShadowViewPresence(_ isFirstResponder: Bool) { guard isFirstResponder, view.window?.isPopUpWindow == false else { shadowView.removeFromSuperview() @@ -377,7 +421,7 @@ final class AddressBarViewController: NSViewController, ObservableObject { shadowView.shadowColor = isSuggestionsWindowVisible ? .suggestionsShadow : .clear shadowView.shadowRadius = isSuggestionsWindowVisible ? 8.0 : 0.0 - activeOuterBorderView.isHidden = isSuggestionsWindowVisible + activeOuterBorderView.isHidden = isSuggestionsWindowVisible || view.window?.isKeyWindow != true activeBackgroundView.isHidden = isSuggestionsWindowVisible activeBackgroundViewWithSuggestions.isHidden = !isSuggestionsWindowVisible if isSearchBox { @@ -395,17 +439,21 @@ final class AddressBarViewController: NSViewController, ObservableObject { private func updateMode(value: AddressBarTextField.Value? = nil) { switch value ?? self.addressBarTextField.value { - case .text: self.mode = .editing(isUrl: false) - case .url(urlString: _, url: _, userTyped: let userTyped): self.mode = userTyped ? .editing(isUrl: true) : .browsing + case .text: self.mode = .editing(.text) + case .url(urlString: _, url: _, userTyped: let userTyped): self.mode = userTyped ? .editing(.url) : .browsing case .suggestion(let suggestionViewModel): switch suggestionViewModel.suggestion { - case .phrase, .unknown: self.mode = .editing(isUrl: false) - case .website, .bookmark, .openTab, .historyEntry, .internalPage: self.mode = .editing(isUrl: true) + case .phrase, .unknown: + self.mode = .editing(.text) + case .website, .bookmark, .historyEntry, .internalPage: + self.mode = .editing(.url) + case .openTab: + self.mode = .editing(.openTabSuggestion) } } } - @objc private func refreshAddressBarAppearance(_ sender: Any) { + @objc private func refreshAddressBarAppearance(_ sender: Any?) { self.updateMode() self.addressBarButtonsViewController?.updateButtons() @@ -420,6 +468,7 @@ final class AddressBarViewController: NSViewController, ObservableObject { activeBackgroundView.backgroundColor = NSColor.homePageAddressBarBackground activeBackgroundViewWithSuggestions.borderColor = NSColor.homePageAddressBarBorder activeBackgroundViewWithSuggestions.backgroundColor = NSColor.homePageAddressBarBackground + switchToTabBox.backgroundColor = NSColor.homePageAddressBarBackground } } else { @@ -428,12 +477,14 @@ final class AddressBarViewController: NSViewController, ObservableObject { activeBackgroundView.borderWidth = 2.0 activeBackgroundView.borderColor = accentColor.withAlphaComponent(0.6) activeBackgroundView.backgroundColor = NSColor.addressBarBackground + switchToTabBox.backgroundColor = NSColor.navigationBarBackground.blended(with: .addressBarBackground) activeOuterBorderView.isHidden = !isHomePage } else { activeBackgroundView.borderWidth = 0 activeBackgroundView.borderColor = nil activeBackgroundView.backgroundColor = NSColor.inactiveSearchBarBackground + switchToTabBox.backgroundColor = NSColor.navigationBarBackground.blended(with: .inactiveSearchBarBackground) activeOuterBorderView.isHidden = true } diff --git a/DuckDuckGo/NavigationBar/View/NavigationBar.storyboard b/DuckDuckGo/NavigationBar/View/NavigationBar.storyboard index 2fb09b93d1..142b992105 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBar.storyboard +++ b/DuckDuckGo/NavigationBar/View/NavigationBar.storyboard @@ -1,7 +1,7 @@ - + - + @@ -408,14 +408,14 @@ - + - + - + @@ -436,7 +436,7 @@ - + @@ -447,7 +447,7 @@ - + @@ -464,10 +464,10 @@ - + - + @@ -504,7 +504,7 @@ - + @@ -512,7 +512,7 @@ - + @@ -520,11 +520,64 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -549,12 +602,14 @@ - + + + @@ -573,7 +628,9 @@ + + @@ -591,6 +648,9 @@ + + + @@ -621,7 +681,7 @@