Skip to content

Commit

Permalink
AI Chat Entry point B - Address bar (#3694)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1204167627774280/1208794359260769/f

**Description**:
Add AI Chat to the Omnibar accessory button
  • Loading branch information
Bunn authored Dec 9, 2024
1 parent 9527f74 commit 7d77c7d
Show file tree
Hide file tree
Showing 25 changed files with 480 additions and 131 deletions.
8 changes: 8 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
310E79BD2949CAA5007C49E8 /* FireButtonReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310E79BC2949CAA5007C49E8 /* FireButtonReferenceTests.swift */; };
310ECFDD282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310ECFDC282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift */; };
310EEA2F2CFFCDC60043CA1A /* AIChatSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310EEA2E2CFFCDBF0043CA1A /* AIChatSettingsTests.swift */; };
311711912D00E5500063AC3D /* OmnibarAccessoryHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311711902D00E53A0063AC3D /* OmnibarAccessoryHandling.swift */; };
311BD1AD2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BD1AC2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift */; };
311BD1AF2836BB4200AEF6C1 /* AutofillItemsLockedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BD1AE2836BB4200AEF6C1 /* AutofillItemsLockedView.swift */; };
311BD1B12836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311BD1B02836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift */; };
Expand Down Expand Up @@ -200,6 +201,7 @@
31DE43C42C2C60E800F8C51F /* DuckPlayerModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DE43C32C2C60E800F8C51F /* DuckPlayerModalPresenter.swift */; };
31DE43C62C2DA70A00F8C51F /* DuckPlayer-ModalAnimation.json in Resources */ = {isa = PBXBuildFile; fileRef = 31DE43C52C2DA70A00F8C51F /* DuckPlayer-ModalAnimation.json */; };
31E69A63280F4CB600478327 /* DuckUI in Frameworks */ = {isa = PBXBuildFile; productRef = 31E69A62280F4CB600478327 /* DuckUI */; };
31E77B272D038BBC006F1C9F /* OmnibarAccessoryHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E77B262D038BB9006F1C9F /* OmnibarAccessoryHandlerTests.swift */; };
31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EF52E0281B3BDC0034796E /* AutofillLoginListItemViewModel.swift */; };
3712091E2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3712091D2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift */; };
372A0FF02B2389590033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */; };
Expand Down Expand Up @@ -1463,6 +1465,7 @@
310E79BC2949CAA5007C49E8 /* FireButtonReferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FireButtonReferenceTests.swift; sourceTree = "<group>"; };
310ECFDC282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableAutofillSettingsTableViewCell.swift; sourceTree = "<group>"; };
310EEA2E2CFFCDBF0043CA1A /* AIChatSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIChatSettingsTests.swift; sourceTree = "<group>"; };
311711902D00E53A0063AC3D /* OmnibarAccessoryHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnibarAccessoryHandling.swift; sourceTree = "<group>"; };
311BD1AC2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillItemsEmptyView.swift; sourceTree = "<group>"; };
311BD1AE2836BB4200AEF6C1 /* AutofillItemsLockedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillItemsLockedView.swift; sourceTree = "<group>"; };
311BD1B02836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListAuthenticator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1524,6 +1527,7 @@
31DE43C12C2C480D00F8C51F /* DuckPlayerFeaturePresentationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerFeaturePresentationView.swift; sourceTree = "<group>"; };
31DE43C32C2C60E800F8C51F /* DuckPlayerModalPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerModalPresenter.swift; sourceTree = "<group>"; };
31DE43C52C2DA70A00F8C51F /* DuckPlayer-ModalAnimation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "DuckPlayer-ModalAnimation.json"; sourceTree = "<group>"; };
31E77B262D038BB9006F1C9F /* OmnibarAccessoryHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnibarAccessoryHandlerTests.swift; sourceTree = "<group>"; };
31EF52E0281B3BDC0034796E /* AutofillLoginListItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginListItemViewModel.swift; sourceTree = "<group>"; };
3712091D2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteMessagingStoreErrorHandling.swift; sourceTree = "<group>"; };
372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncMetricsEventsHandler.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3528,6 +3532,7 @@
310EEA2D2CFFCDB60043CA1A /* AIChat */ = {
isa = PBXGroup;
children = (
31E77B262D038BB9006F1C9F /* OmnibarAccessoryHandlerTests.swift */,
310EEA2E2CFFCDBF0043CA1A /* AIChatSettingsTests.swift */,
);
path = AIChat;
Expand All @@ -3536,6 +3541,7 @@
311C79E22CF790270021196A /* AIChat */ = {
isa = PBXGroup;
children = (
311711902D00E53A0063AC3D /* OmnibarAccessoryHandling.swift */,
31043B152CFA5B890028A97F /* AIChatPixelHandler.swift */,
316AA4592CF8E31F00A2ED28 /* AIChatSettings.swift */,
);
Expand Down Expand Up @@ -7774,6 +7780,7 @@
98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */,
CBD4F140279EBFB300B20FD7 /* SwiftUICollectionViewCell.swift in Sources */,
31CC224928369B38001654A4 /* AutofillLoginSettingsListViewController.swift in Sources */,
311711912D00E5500063AC3D /* OmnibarAccessoryHandling.swift in Sources */,
F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */,
F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */,
7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */,
Expand Down Expand Up @@ -8171,6 +8178,7 @@
9F6933192C59BB0300CD6A5D /* OnboardingPixelReporterMock.swift in Sources */,
CBC88EE12C7F834300F0F8C5 /* SpecialErrorPageUserScriptTests.swift in Sources */,
56D0602D2C383FD2003BAEB5 /* OnboardingSuggestedSearchesProviderTests.swift in Sources */,
31E77B272D038BBC006F1C9F /* OmnibarAccessoryHandlerTests.swift in Sources */,
569437242BDD405400C0881B /* SyncBookmarksAdapterTests.swift in Sources */,
858479CD2B87964500D156C1 /* HistoryManagerTests.swift in Sources */,
CBCCF96828885DEE006F4A71 /* AppPrivacyConfigurationTests.swift in Sources */,
Expand Down
56 changes: 49 additions & 7 deletions DuckDuckGo/AIChat/AIChatSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct AIChatSettings: AIChatSettingsProvider {

var defaultValue: String {
switch self {
/// https://app.asana.com/0/1208541424548398/1208567543352020/f
case .aiChatURL: return "https://duckduckgo.com/?q=DuckDuckGo+AI+Chat&ia=chat&duckai=4"
}
}
Expand All @@ -41,11 +42,14 @@ struct AIChatSettings: AIChatSettingsProvider {
}
private let internalUserDecider: InternalUserDecider
private let userDefaults: UserDefaults
private let notificationCenter: NotificationCenter

init(privacyConfigurationManager: PrivacyConfigurationManaging, internalUserDecider: InternalUserDecider, userDefaults: UserDefaults = .standard) {
init(privacyConfigurationManager: PrivacyConfigurationManaging, internalUserDecider: InternalUserDecider, userDefaults: UserDefaults = .standard,
notificationCenter: NotificationCenter = .default) {
self.internalUserDecider = internalUserDecider
self.privacyConfigurationManager = privacyConfigurationManager
self.userDefaults = userDefaults
self.notificationCenter = notificationCenter
}

// MARK: - Public
Expand All @@ -58,26 +62,47 @@ struct AIChatSettings: AIChatSettingsProvider {
}

var isAIChatBrowsingMenuUserSettingsEnabled: Bool {
userDefaults.showAIChatBrowsingMenu
userDefaults.showAIChatBrowsingMenu && isAIChatBrowsingMenubarShortcutFeatureEnabled
}

var isAIChatAddressBarUserSettingsEnabled: Bool {
userDefaults.showAIChatAddressBar && isAIChatAddressBarShortcutFeatureEnabled
}

var isAIChatFeatureEnabled: Bool {
privacyConfigurationManager.privacyConfig.isEnabled(featureKey: .aiChat) || internalUserDecider.isInternalUser
}

var isAIChatBrowsingToolbarShortcutFeatureEnabled: Bool {
let isBrowsingToolbarShortcutFeatureFlagEnabled = privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(AIChatSubfeature.browsingToolbarShortcut)
let isInternalUser = internalUserDecider.isInternalUser
let isFeatureEnabled = isBrowsingToolbarShortcutFeatureFlagEnabled || isInternalUser
return isFeatureEnabled && isAIChatBrowsingMenuUserSettingsEnabled
var isAIChatAddressBarShortcutFeatureEnabled: Bool {
return isFeatureEnabled(for: .addressBarShortcut)
}

var isAIChatBrowsingMenubarShortcutFeatureEnabled: Bool {
return isFeatureEnabled(for: .browsingToolbarShortcut)
}

func enableAIChatBrowsingMenuUserSettings(enable: Bool) {
userDefaults.showAIChatBrowsingMenu = enable
triggerSettingsChangedNotification()
}

func enableAIChatAddressBarUserSettings(enable: Bool) {
userDefaults.showAIChatAddressBar = enable
triggerSettingsChangedNotification()
}

// MARK: - Private

private func triggerSettingsChangedNotification() {
notificationCenter.post(name: .aiChatSettingsChanged, object: nil)
}

private func isFeatureEnabled(for subfeature: AIChatSubfeature) -> Bool {
let isSubfeatureFlagEnabled = privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(subfeature)
let isInternalUser = internalUserDecider.isInternalUser
return (isSubfeatureFlagEnabled || isInternalUser)
}

private func getSettingsData(_ value: SettingsValue) -> String {
if let value = remoteSettings[value.rawValue] as? String {
return value
Expand All @@ -91,9 +116,11 @@ struct AIChatSettings: AIChatSettingsProvider {
private extension UserDefaults {
enum Keys {
static let showAIChatBrowsingMenu = "aichat.settings.showAIChatBrowsingMenu"
static let showAIChatAddressBar = "aichat.settings.showAIChatAddressBar"
}

static let showAIChatBrowsingMenuDefaultValue = true
static let showAIChatAddressBarDefaultValue = true

@objc dynamic var showAIChatBrowsingMenu: Bool {
get {
Expand All @@ -105,4 +132,19 @@ private extension UserDefaults {
set(newValue, forKey: Keys.showAIChatBrowsingMenu)
}
}

@objc dynamic var showAIChatAddressBar: Bool {
get {
value(forKey: Keys.showAIChatAddressBar) as? Bool ?? Self.showAIChatAddressBarDefaultValue
}

set {
guard newValue != showAIChatAddressBar else { return }
set(newValue, forKey: Keys.showAIChatAddressBar)
}
}
}

public extension NSNotification.Name {
static let aiChatSettingsChanged = Notification.Name("com.duckduckgo.aichat.settings.changed")
}
38 changes: 38 additions & 0 deletions DuckDuckGo/AIChat/OmnibarAccessoryHandling.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// OmnibarAccessoryHandling.swift
// DuckDuckGo
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import AIChat

protocol OmnibarAccessoryHandling {
func omnibarAccessory(for url: URL?) -> OmniBar.AccessoryType
}

struct OmnibarAccessoryHandler: OmnibarAccessoryHandling {
let settings: AIChatSettingsProvider

func omnibarAccessory(for url: URL?) -> OmniBar.AccessoryType {
guard settings.isAIChatFeatureEnabled,
settings.isAIChatAddressBarUserSettingsEnabled else {
return .share
}

return (url?.isDuckDuckGoSearch == true) ? .chat : .share
}
}
15 changes: 5 additions & 10 deletions DuckDuckGo/Base.lproj/OmniBar.xib
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="ipad12_9rounded" orientation="landscape" layout="fullscreen" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
Expand Down Expand Up @@ -340,7 +340,7 @@
</constraints>
<state key="normal" image="Share-24"/>
<connections>
<action selector="onSharePressed:" destination="ojX-nM-dyN" eventType="touchUpInside" id="XwJ-D9-vKG"/>
<action selector="onAccessoryPressed:" destination="ojX-nM-dyN" eventType="touchUpInside" id="Huv-iA-xXy"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1rB-5w-de3">
Expand Down Expand Up @@ -410,6 +410,7 @@
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="abortButton" destination="gb5-wL-4OW" id="khG-dO-p4p"/>
<outlet property="accessoryButton" destination="hor-Jd-3kw" id="vuG-4B-vl0"/>
<outlet property="backButton" destination="i9n-fD-ncJ" id="5sk-Ld-2Pq"/>
<outlet property="bookmarksButton" destination="ii4-Sq-bD1" id="XN8-Yb-eXK"/>
<outlet property="cancelButton" destination="IHT-9R-ERy" id="XVf-QQ-jSa"/>
Expand All @@ -434,7 +435,6 @@
<outlet property="separatorToBottom" destination="nfs-pA-DD4" id="hfJ-iR-yz8"/>
<outlet property="separatorView" destination="hD7-j4-fpl" id="xlg-WZ-peq"/>
<outlet property="settingsButton" destination="1rB-5w-de3" id="KCF-tv-9xx"/>
<outlet property="shareButton" destination="hor-Jd-3kw" id="tc5-Us-Mba"/>
<outlet property="textField" destination="fqM-N4-jNd" id="wRU-Ha-HpH"/>
<outlet property="voiceSearchButton" destination="ita-iD-8ad" id="6ms-wg-lln"/>
</connections>
Expand All @@ -451,11 +451,6 @@
</connections>
</tapGestureRecognizer>
</objects>
<designables>
<designable name="fqM-N4-jNd">
<size key="intrinsicContentSize" width="5" height="21"/>
</designable>
</designables>
<resources>
<image name="Book-24" width="24" height="24"/>
<image name="BrowseNext" width="24" height="24"/>
Expand All @@ -472,7 +467,7 @@
<color red="0.96078431372549022" green="0.96078431372549022" blue="0.96078431372549022" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<systemColor name="systemGreenColor">
<color red="0.20392156859999999" green="0.78039215689999997" blue="0.34901960780000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.20392156862745098" green="0.7803921568627451" blue="0.34901960784313724" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemYellowColor">
<color red="1" green="0.80000000000000004" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
Expand Down
12 changes: 6 additions & 6 deletions DuckDuckGo/LargeOmniBarState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct LargeOmniBarState {
let showBackButton: Bool = true
let showForwardButton: Bool = true
let showBookmarksButton: Bool = true
let showShareButton: Bool = false
let showAccessoryButton: Bool = false
let clearTextOnStart = true
let allowsTrackersAnimation = false
let showPrivacyIcon = false
Expand Down Expand Up @@ -68,7 +68,7 @@ struct LargeOmniBarState {
let showBackButton: Bool = true
let showForwardButton: Bool = true
let showBookmarksButton: Bool = true
let showShareButton: Bool = false
let showAccessoryButton: Bool = false
let clearTextOnStart = false
let allowsTrackersAnimation = false
let showPrivacyIcon = false
Expand Down Expand Up @@ -101,7 +101,7 @@ struct LargeOmniBarState {
let showBackButton: Bool = true
let showForwardButton: Bool = true
let showBookmarksButton: Bool = true
let showShareButton: Bool = false
let showAccessoryButton: Bool = false
let clearTextOnStart = true
let allowsTrackersAnimation = false
let showSearchLoupe = true
Expand Down Expand Up @@ -134,7 +134,7 @@ struct LargeOmniBarState {
let showBackButton: Bool = true
let showForwardButton: Bool = true
let showBookmarksButton: Bool = true
let showShareButton: Bool = true
let showAccessoryButton: Bool = true
let clearTextOnStart = true
let allowsTrackersAnimation = false
let showPrivacyIcon = false
Expand Down Expand Up @@ -167,7 +167,7 @@ struct LargeOmniBarState {
let showBackButton: Bool = true
let showForwardButton: Bool = true
let showBookmarksButton: Bool = true
let showShareButton: Bool = true
let showAccessoryButton: Bool = true
let clearTextOnStart = false
let allowsTrackersAnimation = false
let showPrivacyIcon = false
Expand Down Expand Up @@ -200,7 +200,7 @@ struct LargeOmniBarState {
let showBackButton: Bool = true
let showForwardButton: Bool = true
let showBookmarksButton: Bool = true
let showShareButton: Bool = true
let showAccessoryButton: Bool = true
let clearTextOnStart = false
let allowsTrackersAnimation = true
let showSearchLoupe = false
Expand Down
Loading

0 comments on commit 7d77c7d

Please sign in to comment.