Skip to content

Commit 5a77d61

Browse files
authored
[macOS] - Design feedback fixes for aichat omnibar (#2701)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1204167627774280/task/1212223846179381?focus=true ### Description Fixes: * Do not show bookmark button when URL is focused * Increase address bar input size * Only display the "Press tab then enter" placeholder on the new tab page * Shift + Enter injects new line on duck.ai * Going back to duck.ai does not removes the new lines
1 parent cb9d52b commit 5a77d61

File tree

9 files changed

+88
-26
lines changed

9 files changed

+88
-26
lines changed

macOS/DuckDuckGo/AIChat/AIChatOmnibarTextContainerViewController.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ import Combine
2222
final class AIChatOmnibarTextContainerViewController: NSViewController, ThemeUpdateListening, NSTextViewDelegate {
2323

2424
private enum Constants {
25-
static let bottomPadding: CGFloat = 54.0
26-
static let minimumPanelHeight: CGFloat = 100.0
25+
static let bottomPadding: CGFloat = 34.0
26+
static let minimumPanelHeight: CGFloat = 60
2727
static let maximumPanelHeight: CGFloat = 512.0
2828
static let dividerLeadingOffset: CGFloat = -9.0
2929
static let dividerTrailingOffset: CGFloat = 77.0
30-
static let dividerTopOffset: CGFloat = 8.0
30+
static let dividerTopOffset: CGFloat = -10.0
3131
}
3232

3333
private let backgroundView = MouseBlockingBackgroundView()
@@ -155,7 +155,7 @@ final class AIChatOmnibarTextContainerViewController: NSViewController, ThemeUpd
155155
backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -4),
156156
backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
157157

158-
containerView.topAnchor.constraint(equalTo: backgroundView.topAnchor),
158+
containerView.topAnchor.constraint(equalTo: backgroundView.topAnchor, constant: 1.0),
159159
containerView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor),
160160
containerView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor),
161161
containerView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor),
@@ -254,7 +254,7 @@ final class AIChatOmnibarTextContainerViewController: NSViewController, ThemeUpd
254254
let usedRect = layoutManager.usedRect(for: textContainer)
255255
let textInsets = textView.textContainerInset
256256
let bottomSpacing: CGFloat = Constants.bottomPadding
257-
let totalHeight = usedRect.height + textInsets.height + textInsets.height + 20 + bottomSpacing
257+
let totalHeight = usedRect.height + textInsets.height + bottomSpacing
258258

259259
return min(totalHeight, Constants.maximumPanelHeight)
260260
}
@@ -300,6 +300,10 @@ final class AIChatOmnibarTextContainerViewController: NSViewController, ThemeUpd
300300
view.window?.makeFirstResponder(textView)
301301
}
302302

303+
func insertNewline() {
304+
textView.insertNewlineIgnoringFieldEditor(nil)
305+
}
306+
303307
func updateScrollingBehavior(maxHeight: CGFloat) {
304308
let desiredHeight = calculateDesiredPanelHeight()
305309
let effectiveMaxHeight = min(maxHeight, Constants.maximumPanelHeight)

macOS/DuckDuckGo/MainWindow/MainView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ final class MainView: NSView {
3333
static let findInPageContainerTopOffset: CGFloat = -4
3434
static let fireContainerHeight: CGFloat = 32
3535
static let bannerHeight: CGFloat = 48
36-
static let aiChatOmnibarContainerMinHeight: CGFloat = 100
36+
static let aiChatOmnibarContainerMinHeight: CGFloat = 60
3737
static let aiChatOmnibarContainerPadding: CGFloat = 50
3838
}
3939

macOS/DuckDuckGo/MainWindow/MainViewController.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,11 @@ extension MainViewController {
10091009
if flags.contains(.shift) || flags.contains(.option),
10101010
featureFlagger.isFeatureOn(.aiChatOmnibarToggle),
10111011
let buttonsViewController = navigationBarViewController.addressBarViewController?.addressBarButtonsViewController {
1012+
let isSwitchingToAIChatMode = buttonsViewController.searchModeToggleControl?.selectedSegment == 0
10121013
buttonsViewController.toggleSearchMode()
1014+
if isSwitchingToAIChatMode {
1015+
self.aiChatOmnibarTextContainerViewController.insertNewline()
1016+
}
10131017
return true
10141018
} else if flags.contains(.control),
10151019
featureFlagger.isFeatureOn(.aiChatOmnibarToggle) {

macOS/DuckDuckGo/NavigationBar/AddressBarSharedTextState.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,28 @@ final class AddressBarSharedTextState: ObservableObject {
3232
/// Whether the user has typed anything (triggers text sharing between modes)
3333
@Published private(set) var hasUserInteractedWithText: Bool = false
3434

35+
/// Whether the user has type anything after switching modes
36+
private(set) var hasUserInteractedWithTextAfterSwitchingModes: Bool = false
37+
3538
/// Resets the shared state to initial values
3639
func reset() {
3740
text = ""
3841
selectionRange = NSRange(location: 0, length: 0)
3942
hasUserInteractedWithText = false
4043
}
4144

45+
func resetUserInteraction() {
46+
hasUserInteractedWithText = false
47+
}
48+
49+
func setHasUserInteractedWithTextAfterSwitchingModes(_ value: Bool) {
50+
hasUserInteractedWithTextAfterSwitchingModes = value
51+
}
52+
53+
func resetUserInteractionAfterSwitchingModes() {
54+
hasUserInteractedWithTextAfterSwitchingModes = false
55+
}
56+
4257
/// Updates the shared text content
4358
/// - Parameters:
4459
/// - newText: The new text value
@@ -47,6 +62,7 @@ final class AddressBarSharedTextState: ObservableObject {
4762
text = newText
4863
if markInteraction && !newText.isEmpty {
4964
hasUserInteractedWithText = true
65+
hasUserInteractedWithTextAfterSwitchingModes = true
5066
}
5167

5268
// Adjust selection range if it's now beyond the text length

macOS/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,12 @@ final class AddressBarButtonsViewController: NSViewController {
999999
return
10001000
}
10011001

1002+
if isTextFieldEditorFirstResponder && featureFlagger.isFeatureOn(.aiChatOmnibarToggle) {
1003+
bookmarkButton.isShown = false
1004+
updateAIChatDividerVisibility()
1005+
return
1006+
}
1007+
10021008
let hasEmptyAddressBar = textFieldValue?.isEmpty ?? true
10031009
var shouldShowBookmarkButton: Bool {
10041010
guard let tabViewModel, tabViewModel.canBeBookmarked else { return false }

macOS/DuckDuckGo/NavigationBar/View/AddressBarViewController.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -684,10 +684,12 @@ final class AddressBarViewController: NSViewController {
684684
&& aiChatSettings.isAIFeaturesEnabled
685685
&& aiChatSettings.showSearchAndDuckAIToggle
686686

687-
if shouldShowDuckAIHint {
688-
addressBarPlaceholder = UserText.addressBarPlaceholderWithDuckAI
689-
} else if isNewTab {
690-
addressBarPlaceholder = UserText.addressBarPlaceholder
687+
if isNewTab {
688+
if shouldShowDuckAIHint {
689+
addressBarPlaceholder = UserText.addressBarPlaceholderWithDuckAI
690+
} else {
691+
addressBarPlaceholder = UserText.addressBarPlaceholder
692+
}
691693
} else {
692694
addressBarPlaceholder = ""
693695
}
@@ -871,6 +873,8 @@ final class AddressBarViewController: NSViewController {
871873
delegate?.resizeAddressBarForHomePage(self)
872874
addressBarButtonsViewController?.setupButtonPaddings(isFocused: false)
873875
}
876+
877+
setupAddressBarPlaceHolder()
874878
}
875879

876880
private func handleFirstResponderChange() {
@@ -1091,7 +1095,7 @@ extension AddressBarViewController: AddressBarButtonsViewControllerDelegate {
10911095
if isAIChatMode {
10921096
if mode.isEditing {
10931097
let text = addressBarTextField.stringValueWithoutSuffix
1094-
if !text.isEmpty {
1098+
if !text.isEmpty && sharedTextState.hasUserInteractedWithTextAfterSwitchingModes == true {
10951099
sharedTextState.updateText(text, markInteraction: false)
10961100
}
10971101
}
@@ -1111,11 +1115,14 @@ extension AddressBarViewController: AddressBarButtonsViewControllerDelegate {
11111115
updateMode()
11121116
addressBarTextField.makeMeFirstResponder()
11131117

1118+
/// Force layout update after becoming first responder to update in case the window was resized
1119+
layoutTextFields(withMinX: addressBarButtonsViewController.buttonsWidth)
1120+
11141121
if shouldRestoreFromSharedState {
11151122
addressBarTextField.setCursorPositionAfterRestore()
11161123
}
11171124
}
1118-
1125+
sharedTextState.resetUserInteractionAfterSwitchingModes()
11191126
delegate?.addressBarViewControllerSearchModeToggleChanged(self, isAIChatMode: isAIChatMode)
11201127
}
11211128

macOS/DuckDuckGo/Suggestions/View/SuggestionViewController.swift

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,14 @@ final class SuggestionViewController: NSViewController {
6565
}
6666

6767
private var suggestionResultCancellable: AnyCancellable?
68-
private var selectionIndexCancellable: AnyCancellable?
68+
private var selectionSyncCancellable: AnyCancellable?
6969

7070
private var eventMonitorCancellables = Set<AnyCancellable>()
7171
private var appObserver: Any?
7272

73+
/// Flag to prevent re-entrancy when programmatically updating table selection
74+
private var isUpdatingTableSelection = false
75+
7376
override func viewDidLoad() {
7477
super.viewDidLoad()
7578

@@ -79,7 +82,7 @@ final class SuggestionViewController: NSViewController {
7982
setupTableView()
8083
addTrackingArea()
8184
subscribeToSuggestionResult()
82-
subscribeToSelectionIndex()
85+
subscribeToSelectionSync()
8386
subscribeToThemeChanges()
8487

8588
applyThemeStyle()
@@ -157,11 +160,14 @@ final class SuggestionViewController: NSViewController {
157160
}
158161
}
159162

160-
private func subscribeToSelectionIndex() {
161-
selectionIndexCancellable = suggestionContainerViewModel.$selectedRowIndex.receive(on: DispatchQueue.main).sink { [weak self] selectedRowIndex in
162-
guard let self else { return }
163-
self.selectTableRow(at: selectedRowIndex)
164-
}
163+
/// Subscribes to view model selection changes (e.g., from keyboard navigation)
164+
private func subscribeToSelectionSync() {
165+
selectionSyncCancellable = suggestionContainerViewModel.$selectedRowIndex
166+
.receive(on: DispatchQueue.main)
167+
.sink { [weak self] _ in
168+
guard let self, !self.isUpdatingTableSelection else { return }
169+
self.syncTableSelectionWithViewModel()
170+
}
165171
}
166172

167173
private func displayNewSuggestions() {
@@ -185,24 +191,32 @@ final class SuggestionViewController: NSViewController {
185191
suggestionContainerViewModel.selectRow(at: selectedRowCache)
186192
}
187193

188-
self.selectTableRow(at: self.suggestionContainerViewModel.selectedRowIndex)
194+
syncTableSelectionWithViewModel()
189195
}
190196
}
191197

198+
func syncTableSelectionWithViewModel() {
199+
selectTableRow(at: suggestionContainerViewModel.selectedRowIndex)
200+
}
201+
192202
private func selectTableRow(at rowIndex: Int?) {
193203
if tableView.selectedRow == rowIndex {
194204
if let rowIndex, let cell = tableView.view(atColumn: 0, row: rowIndex, makeIfNecessary: false) as? SuggestionTableCellView {
195-
// Show the delete button if necessary
196205
cell.updateDeleteImageViewVisibility()
197206
}
198207
return
199208
}
200209

210+
isUpdatingTableSelection = true
211+
defer { isUpdatingTableSelection = false }
212+
201213
guard let rowIndex,
202214
rowIndex >= 0,
203215
rowIndex < suggestionContainerViewModel.numberOfRows else {
204216
if let defaultRow = suggestionContainerViewModel.defaultSelectedRow {
205217
tableView.selectRowIndexes(IndexSet(integer: defaultRow), byExtendingSelection: false)
218+
// Sync view model with the default selection so keyboard navigation works correctly
219+
suggestionContainerViewModel.selectRow(at: defaultRow)
206220
} else {
207221
self.clearSelection()
208222
}
@@ -218,6 +232,7 @@ final class SuggestionViewController: NSViewController {
218232

219233
guard tableRow >= 0 else {
220234
suggestionContainerViewModel.clearRowSelection()
235+
syncTableSelectionWithViewModel()
221236
return
222237
}
223238

@@ -226,6 +241,7 @@ final class SuggestionViewController: NSViewController {
226241
}
227242

228243
suggestionContainerViewModel.selectRow(at: tableRow)
244+
syncTableSelectionWithViewModel()
229245
}
230246

231247
private func clearSelection() {
@@ -424,6 +440,8 @@ extension SuggestionViewController: NSTableViewDelegate {
424440
}
425441

426442
func tableViewSelectionDidChange(_ notification: Notification) {
443+
guard !isUpdatingTableSelection else { return }
444+
427445
if tableView.selectedRow == -1 {
428446
suggestionContainerViewModel.clearRowSelection()
429447
return

macOS/DuckDuckGo/Suggestions/ViewModel/SuggestionContainerViewModel.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,14 +253,21 @@ final class SuggestionContainerViewModel {
253253
func selectRow(at rowIndex: Int) {
254254
guard rowIndex >= 0, rowIndex < numberOfRows else {
255255
Logger.general.error("SuggestionContainerViewModel: Row index out of bounds")
256-
selectedRowIndex = nil
256+
if selectedRowIndex != nil {
257+
selectedRowIndex = nil
258+
selectionIndex = nil
259+
}
257260
return
258261
}
262+
263+
guard selectedRowIndex != rowIndex else { return }
264+
259265
selectedRowIndex = rowIndex
260266
selectionIndex = selectionIndex(forRow: rowIndex)
261267
}
262268

263269
func clearRowSelection() {
270+
guard selectedRowIndex != nil || selectionIndex != nil else { return }
264271
selectedRowIndex = nil
265272
selectionIndex = nil
266273
}

macOS/DuckDuckGo/VisualRefresh/AddressBarStyleProviding.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,14 @@ final class CurrentAddressBarStyleProvider: AddressBarStyleProviding {
132132
private let navigationBarHeightForHomePage: CGFloat = 52
133133
private let navigationBarHeightForPopUpWindow: CGFloat = 42
134134
private let addressBarTopPaddingForDefault: CGFloat = 7
135-
private let addressBarTopPaddingForDefaultFocusedWithAIChat: CGFloat = 4
135+
private let addressBarTopPaddingForDefaultFocusedWithAIChat: CGFloat = 3
136136
private let addressBarTopPaddingForHomePage: CGFloat = 7
137-
private let addressBarTopPaddingForHomePageFocusedWithAIChat: CGFloat = 4
137+
private let addressBarTopPaddingForHomePageFocusedWithAIChat: CGFloat = 3
138138
private let addressBarTopPaddingForPopUpWindow: CGFloat = 7
139139
private let addressBarBottomPaddingForDefault: CGFloat = 7
140-
private let addressBarBottomPaddingForDefaultFocusedWithAIChat: CGFloat = 4
140+
private let addressBarBottomPaddingForDefaultFocusedWithAIChat: CGFloat = 3
141141
private let addressBarBottomPaddingForHomePage: CGFloat = 7
142-
private let addressBarBottomPaddingForHomePageFocusedWithAIChat: CGFloat = 4
142+
private let addressBarBottomPaddingForHomePageFocusedWithAIChat: CGFloat = 3
143143
private let addressBarBottomPaddingForPopUpWindow: CGFloat = 7
144144

145145
private let featureFlagger: FeatureFlagger

0 commit comments

Comments
 (0)