Skip to content

Duck.ai voice mode entry point in input screen#7979

Merged
aitorvs merged 3 commits intodevelopfrom
feature/aitor/duck-ai-voice-entry-point
Mar 16, 2026
Merged

Duck.ai voice mode entry point in input screen#7979
aitorvs merged 3 commits intodevelopfrom
feature/aitor/duck-ai-voice-entry-point

Conversation

@aitorvs
Copy link
Collaborator

@aitorvs aitorvs commented Mar 15, 2026

Task/Issue URL: https://app.asana.com/1/137249556945/project/1211654189969294/task/1213651299034811

Description

Adds a mic button on the duck.ai tab of the input screen that opens duck.ai directly in voice mode (duck.ai/?mode=voice-mode), giving users 1-click access to voice chat.

  • New duckAiVoiceEntryPoint sub-feature flag (DefaultFeatureValue.INTERNAL — off in production, on in internal builds)
  • New openVoiceDuckChat() on the DuckChat API → implemented in RealDuckChat by appending mode=voice-mode and forcing a new session
  • InputScreenViewModel: extended the voice button visibility combine block to be tab-aware; duck.ai tab + flag on → button follows voiceInputAllowed (text presence) rather than VoiceSearchAvailability
  • InputScreenFragment: both voice click handlers route to viewModel.onVoiceEntryTapped() on the duck.ai tab when flag is on
  • Pixel: m_aichat_voice_entry_tapped fired on tap
  • 7 new unit tests in InputScreenViewModelTest

Steps to test this PR

Enable flag

  • In internal build, enable duckAiVoiceEntryPoint under duck.ai feature flags

Duck.ai tab — empty field

  • Open the input screen and switch to the duck.ai tab
  • Mic button is visible (even if private voice search is disabled in settings)
  • Tap the mic button → duck.ai opens with ?mode=voice-mode in the URL, fresh session

Duck.ai tab — with text

  • Type something in the input field → mic button disappears, send button appears
  • Clear the field → mic button reappears

Search tab (unchanged)

  • Switch to the search tab → mic button follows voice search availability as before
  • Tap mic → system voice recognition launches (not duck.ai voice mode)

Flag off

  • Disable duckAiVoiceEntryPoint → duck.ai tab mic button behaves as before (follows voice search availability, launches system voice recognition)

UI changes

Before After
(Upload before screenshot) (Upload after screenshot)

Note

Medium Risk
Changes input-screen voice button behavior behind a new feature flag and adds a new DuckChat navigation path that forces a fresh session; risk is moderate due to UI/flow branching and URL/session handling.

Overview
Adds a new, flag-gated Duck.ai voice entry point on the input screen: when on the Duck.ai tab and duckAiVoiceEntryPoint is enabled, the mic button opens Duck.ai with mode=voice-mode (forcing a new session) instead of launching system voice search.

Introduces DuckChat.openVoiceDuckChat() (implemented in RealDuckChat via mode=voice-mode query param), updates voice button visibility logic in InputScreenViewModel to be tab-aware, and wires both voice click handlers in InputScreenFragment to the new behavior. Adds new pixels (m_aichat_voice_entry_tapped_*) plus a shared fireCountAndDaily helper, and expands unit test coverage for the new routing/visibility/session semantics.

Written by Cursor Bugbot for commit 0ed7e9a. This will update automatically on new commits. Configure here.

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Duplicated voice click routing logic in two handlers
    • Extracted the duplicated voice-entry routing block into a shared handleVoiceClick() method and reused it in both voice click handlers.

Create PR

Or push these changes by commenting:

@cursor push 6790996ab1
Preview (6790996ab1)
diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/InputScreenFragment.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/InputScreenFragment.kt
--- a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/InputScreenFragment.kt
+++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/inputscreen/ui/InputScreenFragment.kt
@@ -499,12 +499,7 @@
                 viewModel.onBrowserMenuTapped()
             }
             onVoiceClick = {
-                val isChatTab = inputModeWidget.isChatTabSelected()
-                if (isChatTab && duckChatFeature.duckAiVoiceEntryPoint().isEnabled()) {
-                    viewModel.onVoiceEntryTapped()
-                } else {
-                    voiceSearchLauncher.launch(requireActivity(), VoiceSearchMode.fromValue(inputModeWidget.getSelectedTabPosition()))
-                }
+                handleVoiceClick()
             }
             onClearTextTapped = {
                 viewModel.onClearTextTapped()
@@ -583,15 +578,19 @@
             pixel.fire(DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_OMNIBAR_FLOATING_RETURN_PRESSED)
         }
         inputScreenButtons.onVoiceClick = {
-            val isChatTab = inputModeWidget.isChatTabSelected()
-            if (isChatTab && duckChatFeature.duckAiVoiceEntryPoint().isEnabled()) {
-                viewModel.onVoiceEntryTapped()
-            } else {
-                voiceSearchLauncher.launch(requireActivity(), VoiceSearchMode.fromValue(inputModeWidget.getSelectedTabPosition()))
-            }
+            handleVoiceClick()
         }
     }
 
+    private fun handleVoiceClick() {
+        val isChatTab = inputModeWidget.isChatTabSelected()
+        if (isChatTab && duckChatFeature.duckAiVoiceEntryPoint().isEnabled()) {
+            viewModel.onVoiceEntryTapped()
+        } else {
+            voiceSearchLauncher.launch(requireActivity(), VoiceSearchMode.fromValue(inputModeWidget.getSelectedTabPosition()))
+        }
+    }
+
     private fun configureLogoAnimation() =
         with(binding.ddgLogo) {
             setMinAndMaxFrame(0, LOGO_MAX_FRAME)

viewModel.onVoiceEntryTapped()
} else {
voiceSearchLauncher.launch(requireActivity(), VoiceSearchMode.fromValue(inputModeWidget.getSelectedTabPosition()))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated voice click routing logic in two handlers

Low Severity

The new voice-entry-point routing logic (check isChatTabSelected(), check feature flag, branch to onVoiceEntryTapped() vs voiceSearchLauncher.launch(...)) is copy-pasted identically in both inputModeWidget.onVoiceClick and inputScreenButtons.onVoiceClick. If the routing logic ever changes (e.g., new conditions or actions), only one handler might get updated, causing inconsistent behavior depending on which voice button the user taps. Extracting this into a shared private method would eliminate the risk.


Please tell me if this was useful or not with a 👍 or 👎.

Additional Locations (1)
Fix in Cursor Fix in Web

aitorvs and others added 2 commits March 15, 2026 14:18
Adds a new `duckAiVoiceEntryPoint` feature flag (default: internal) that,
when enabled, replaces the system voice recognition button on the duck.ai
tab with a direct link to duck.ai/?mode=voice-mode. The search tab keeps
its existing voice-to-text behaviour unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use isChatTabSelected() directly instead of falling back on
visibilityState.searchMode, which can be stale after a tab switch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@aitorvs aitorvs force-pushed the feature/aitor/duck-ai-voice-entry-point branch from 90da8e0 to 9a97b63 Compare March 15, 2026 14:39
Copy link
Collaborator Author

aitorvs commented Mar 15, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

override fun openVoiceDuckChat() {
logcat { "Duck.ai: openVoiceDuckChat" }
val parameters = mapOf(MODE_QUERY_NAME to VOICE_MODE_QUERY_VALUE)
openDuckChat(parameters, forceNewSession = true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs tests

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

Copy link
Contributor

@joshliebe joshliebe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works well, nice one 🙂
Only missing tests on RealDuckChat

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@aitorvs aitorvs merged commit e35d800 into develop Mar 16, 2026
13 checks passed
@aitorvs aitorvs deleted the feature/aitor/duck-ai-voice-entry-point branch March 16, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants