Skip to content

Commit c3cfa14

Browse files
malmsteinclaude
andcommitted
Tune contextual UTI controls and input background
Set the contextual input container background per sheet mode: surface in the initial (INPUT) state and the Duck.ai page colour in webview mode, so the composer reads correctly against each backdrop. Revert the always-visible bottom row back to the focus-gated behaviour (matching the omnibar) and re-assert the plugin containers' visibility on focus in the contextual widget, so the tools row reveals with its controls when the input is focused. Known limitation: after the sheet is closed and reopened, the plugin controls can stay hidden until a full re-render (send a prompt / New chat). Tracked for follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a2639ad commit c3cfa14

4 files changed

Lines changed: 33 additions & 152 deletions

File tree

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/contextual/DuckChatContextualFragment.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import com.duckduckgo.common.ui.DuckDuckGoFragment
6464
import com.duckduckgo.common.ui.menu.PopupMenu
6565
import com.duckduckgo.common.ui.view.PopupMenuItemView
6666
import com.duckduckgo.common.ui.view.dialog.ActionBottomSheetDialog
67+
import com.duckduckgo.common.ui.view.getColorFromAttr
6768
import com.duckduckgo.common.ui.view.gone
6869
import com.duckduckgo.common.ui.view.makeSnackbarWithNoBottomInset
6970
import com.duckduckgo.common.ui.view.show
@@ -707,6 +708,9 @@ class DuckChatContextualFragment :
707708
DuckChatContextualViewModel.SheetMode.INPUT -> {
708709
binding.contextualWebviewContainer.gone()
709710
binding.contextualModePrompts.show()
711+
binding.contextualInputContainer.setBackgroundColor(
712+
requireContext().getColorFromAttr(com.duckduckgo.mobile.android.R.attr.daxColorSurface),
713+
)
710714
contextualNativeInputManager.onInputMode()
711715

712716
if (viewState.showChatsIcon) {
@@ -747,6 +751,9 @@ class DuckChatContextualFragment :
747751
DuckChatContextualViewModel.SheetMode.WEBVIEW -> {
748752
binding.contextualModeNativeContent.gone()
749753
binding.contextualModePrompts.gone()
754+
binding.contextualInputContainer.setBackgroundColor(
755+
requireContext().getColorFromAttr(com.duckduckgo.mobile.android.R.attr.daxColorDuckAiBackground),
756+
)
750757
binding.contextualWebviewContainer.show()
751758
binding.contextualNewChat.show()
752759
if (viewState.isFireButtonEnabled) binding.contextualFire.show() else binding.contextualFire.gone()

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/nativeinput/views/NativeInputModeWidget.kt

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,10 @@ class NativeInputModeWidget @JvmOverloads constructor(
566566
// setToggleVisible — e.g. re-show the toggle after the keyboard has been dismissed.
567567
// Only animate on focus-gain — focus-loss pairs with an instant setToggleVisible hide,
568568
// and animating the padding shrink afterwards looks like a two-step collapse.
569-
if (hasFocus) beginFocusTransition()
569+
if (hasFocus) {
570+
beginFocusTransition()
571+
reassertContextualPluginVisibility()
572+
}
570573
updateBottomRowVisibility()
571574
applyVerticalPaddingForFocus()
572575
nativeInputState?.let { updateFireButtonVisibility(it) }
@@ -583,17 +586,29 @@ class NativeInputModeWidget @JvmOverloads constructor(
583586

584587
private fun updateBottomRowVisibility() {
585588
val bottomRow = findViewById<View?>(R.id.inputModeWidgetBottomRow) ?: return
586-
val visible = shouldShowBottomRow(
587-
onChatTab = isChatTabSelected(),
588-
isContextual = isContextualWidget,
589-
hasFocus = inputField.hasFocus(),
590-
previewEnterFocus = previewEnterFocus,
591-
isStreaming = isStreaming,
592-
suppress = nativeInputState?.shouldSuppressBottomRow() == true,
593-
)
589+
val suppress = nativeInputState?.shouldSuppressBottomRow() == true
590+
val visible = isChatTabSelected() &&
591+
(inputField.hasFocus() || previewEnterFocus) &&
592+
!isStreaming &&
593+
!suppress
594594
bottomRow.visibility = if (visible) VISIBLE else GONE
595595
}
596596

597+
/**
598+
* In the contextual sheet the widget is reused across the sheet being hidden and shown again;
599+
* on reopen the bottom-row plugin containers can end up hidden (their visibility is set once when
600+
* plugins first load and isn't restored on reuse). Re-assert them here, from the focus path so the
601+
* change rides the focus layout transition and re-measures. Scoped to the contextual widget so the
602+
* omnibar is untouched. The model picker is gated on its enabled state so it stays hidden mid-chat.
603+
*/
604+
private fun reassertContextualPluginVisibility() {
605+
if (!isContextualWidget) return
606+
val onChatTab = isChatTabSelected()
607+
findViewById<View?>(R.id.attachButtonContainer)?.isVisible = onChatTab
608+
findViewById<View?>(R.id.optionsButtonContainer)?.isVisible = onChatTab
609+
findViewById<View?>(R.id.modelPickerContainer)?.isVisible = onChatTab && viewModel.modelPickerEnabled.value
610+
}
611+
597612
private fun updateToggleVisibilityForState() {
598613
if (isDuckAiPageContext()) return
599614
applyToggleVisibility(nativeInputState?.toggleVisible ?: false)
@@ -1616,25 +1631,6 @@ internal fun NativeInputState.shouldSuppressBottomRow(): Boolean =
16161631
inputMode == NativeInputState.InputMode.SEARCH_ONLY &&
16171632
inputContext == NativeInputState.InputContext.BROWSER
16181633

1619-
/**
1620-
* Whether the bottom tools row (attachments, options, reasoning, model picker) should be visible.
1621-
* On the Duck.ai chat tab, while not streaming and not suppressed. In the contextual sheet the widget
1622-
* is the dedicated composer, so the row is always visible there; everywhere else it only reveals once
1623-
* the input has focus (or the pre-focus preview state is active), matching the omnibar behaviour.
1624-
*/
1625-
internal fun shouldShowBottomRow(
1626-
onChatTab: Boolean,
1627-
isContextual: Boolean,
1628-
hasFocus: Boolean,
1629-
previewEnterFocus: Boolean,
1630-
isStreaming: Boolean,
1631-
suppress: Boolean,
1632-
): Boolean =
1633-
onChatTab &&
1634-
(isContextual || hasFocus || previewEnterFocus) &&
1635-
!isStreaming &&
1636-
!suppress
1637-
16381634
/**
16391635
* Bottom-row controls (model / reasoning / options / attachment) are shown only on the Duck.ai
16401636
* chat tab and only when no chat is streaming. While streaming, the bottom row stays visible for

duckchat/duckchat-impl/src/main/res/layout/fragment_contextual_duck_ai_native.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,10 @@
120120
<LinearLayout
121121
android:id="@+id/contextualModePrompts"
122122
android:layout_width="match_parent"
123-
android:layout_height="wrap_content"
123+
android:layout_height="200dp"
124124
android:orientation="vertical"
125125
android:paddingHorizontal="@dimen/keyline_4"
126+
android:gravity="bottom"
126127
android:paddingTop="@dimen/keyline_4"
127128
android:paddingBottom="@dimen/keyline_1"
128129
app:layout_constraintBottom_toTopOf="@id/inputModeWidgetCard"

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/NativeInputModeWidgetBottomRowTest.kt

Lines changed: 0 additions & 123 deletions
This file was deleted.

0 commit comments

Comments
 (0)