From 9151f4f8371ecb4a948f7be08160c6c8de69486b Mon Sep 17 00:00:00 2001 From: Serg Date: Tue, 10 Sep 2024 16:19:21 -0400 Subject: [PATCH] Autofocuses input box on Android Leo chat window on a fresh chat --- .../BraveLeoSettingsLauncherHelper.java | 16 +++++++++ .../brave_leo_settings_launcher_helper.cc | 5 +++ .../brave_leo_settings_launcher_helper.h | 2 ++ browser/ui/webui/ai_chat/ai_chat_ui.cc | 34 +++++++++++++------ browser/ui/webui/ai_chat/ai_chat_ui.h | 2 ++ .../webui/ai_chat/ai_chat_ui_page_handler.cc | 6 ++++ .../webui/ai_chat/ai_chat_ui_page_handler.h | 1 + .../ai_chat/core/common/mojom/ai_chat.mojom | 1 + .../page/components/input_box/index.tsx | 15 +++++++- .../ai_chat/resources/page/state/context.ts | 2 ++ .../page/state/data-context-provider.tsx | 3 ++ .../page/components/BeginGeneration.tsx | 1 + 12 files changed, 76 insertions(+), 12 deletions(-) diff --git a/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoSettingsLauncherHelper.java b/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoSettingsLauncherHelper.java index 4b8891f92674..a26a6039a807 100644 --- a/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoSettingsLauncherHelper.java +++ b/android/java/org/chromium/chrome/browser/brave_leo/BraveLeoSettingsLauncherHelper.java @@ -7,6 +7,7 @@ import android.app.Activity; import android.content.Context; +import android.view.inputmethod.InputMethodManager; import org.jni_zero.CalledByNative; @@ -59,6 +60,21 @@ private static void handleVoiceRecognition( .startVoiceRecognition(); } + @CalledByNative + private static void handleShowSoftKeyboard(WebContents webContents) { + WindowAndroid windowAndroid = webContents.getTopLevelNativeWindow(); + if (windowAndroid == null) { + return; + } + Activity activity = windowAndroid.getActivity().get(); + if (activity == null) { + return; + } + InputMethodManager imm = + (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(activity.getWindow().getCurrentFocus(), 0); + } + @CalledByNative private static void closeActivity(WebContents webContents) { WindowAndroid windowAndroid = webContents.getTopLevelNativeWindow(); diff --git a/browser/ui/android/ai_chat/brave_leo_settings_launcher_helper.cc b/browser/ui/android/ai_chat/brave_leo_settings_launcher_helper.cc index efbea2e25aab..d970087169e6 100644 --- a/browser/ui/android/ai_chat/brave_leo_settings_launcher_helper.cc +++ b/browser/ui/android/ai_chat/brave_leo_settings_launcher_helper.cc @@ -46,4 +46,9 @@ void CloseActivity(content::WebContents* web_contents) { base::android::AttachCurrentThread(), web_contents->GetJavaWebContents()); } +void HandleShowSoftKeyboard(content::WebContents* web_contents) { + Java_BraveLeoSettingsLauncherHelper_handleShowSoftKeyboard( + base::android::AttachCurrentThread(), web_contents->GetJavaWebContents()); +} + } // namespace ai_chat diff --git a/browser/ui/android/ai_chat/brave_leo_settings_launcher_helper.h b/browser/ui/android/ai_chat/brave_leo_settings_launcher_helper.h index b16da99302aa..3e044653262a 100644 --- a/browser/ui/android/ai_chat/brave_leo_settings_launcher_helper.h +++ b/browser/ui/android/ai_chat/brave_leo_settings_launcher_helper.h @@ -27,6 +27,8 @@ void HandleVoiceRecognition(content::WebContents* web_contents, content::WebContents* context_web_contents); // Closes Leo chat window void CloseActivity(content::WebContents* web_contents); +// Shows soft keyboard +void HandleShowSoftKeyboard(content::WebContents* web_contents); } // namespace ai_chat diff --git a/browser/ui/webui/ai_chat/ai_chat_ui.cc b/browser/ui/webui/ai_chat/ai_chat_ui.cc index b326cb5948aa..b082bee606e0 100644 --- a/browser/ui/webui/ai_chat/ai_chat_ui.cc +++ b/browser/ui/webui/ai_chat/ai_chat_ui.cc @@ -10,6 +10,7 @@ #include "brave/browser/ui/side_panel/ai_chat/ai_chat_side_panel_utils.h" #include "brave/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.h" #include "brave/browser/ui/webui/brave_webui_source.h" +#include "brave/components/ai_chat/content/browser/ai_chat_tab_helper.h" #include "brave/components/ai_chat/core/browser/constants.h" #include "brave/components/ai_chat/core/browser/utils.h" #include "brave/components/ai_chat/core/common/pref_names.h" @@ -93,6 +94,11 @@ AIChatUI::AIChatUI(content::WebUI* web_ui) #endif untrusted_source->AddBoolean("isMobile", kIsMobile); + ai_chat::AIChatTabHelper* active_chat_tab_helper = + ai_chat::AIChatTabHelper::FromWebContents(GetChatWebContents()); + untrusted_source->AddBoolean( + "hasInitialHistory", + active_chat_tab_helper->GetVisibleConversationHistory().size() != 0); untrusted_source->AddBoolean( "hasUserDismissedPremiumPrompt", @@ -118,21 +124,13 @@ AIChatUI::AIChatUI(content::WebUI* web_ui) AIChatUI::~AIChatUI() = default; -void AIChatUI::BindInterface( - mojo::PendingReceiver receiver) { - // We call ShowUI() before creating the PageHandler object so that - // the WebContents is added to a Browser which we can get a reference - // to and provide to the PageHandler. - if (embedder_) { - embedder_->ShowUI(); - } - +content::WebContents* AIChatUI::GetChatWebContents() { content::WebContents* web_contents = nullptr; #if !BUILDFLAG(IS_ANDROID) Browser* browser = ai_chat::GetBrowserForWebContents(web_ui()->GetWebContents()); if (!browser) { - return; + return web_contents; } TabStripModel* tab_strip_model = browser->tab_strip_model(); @@ -144,8 +142,22 @@ void AIChatUI::BindInterface( if (web_contents == web_ui()->GetWebContents()) { web_contents = nullptr; } + + return web_contents; +} + +void AIChatUI::BindInterface( + mojo::PendingReceiver receiver) { + // We call ShowUI() before creating the PageHandler object so that + // the WebContents is added to a Browser which we can get a reference + // to and provide to the PageHandler. + if (embedder_) { + embedder_->ShowUI(); + } + page_handler_ = std::make_unique( - web_ui()->GetWebContents(), web_contents, profile_, std::move(receiver)); + web_ui()->GetWebContents(), GetChatWebContents(), profile_, + std::move(receiver)); } bool UntrustedChatUIConfig::IsWebUIEnabled( diff --git a/browser/ui/webui/ai_chat/ai_chat_ui.h b/browser/ui/webui/ai_chat/ai_chat_ui.h index 79cdd2d186ef..965a82a94720 100644 --- a/browser/ui/webui/ai_chat/ai_chat_ui.h +++ b/browser/ui/webui/ai_chat/ai_chat_ui.h @@ -18,6 +18,7 @@ namespace content { class BrowserContext; +class WebContents; } class Profile; @@ -42,6 +43,7 @@ class AIChatUI : public ui::UntrustedWebUIController { static constexpr std::string GetWebUIName() { return "AIChatPanel"; } private: + content::WebContents* GetChatWebContents(); std::unique_ptr page_handler_; base::WeakPtr embedder_; diff --git a/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.cc b/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.cc index 8b2e9ab1b0ee..a3831ad1cadc 100644 --- a/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.cc +++ b/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.cc @@ -172,6 +172,12 @@ void AIChatUIPageHandler::HandleVoiceRecognition() { #endif } +void AIChatUIPageHandler::HandleShowSoftKeyboard() { +#if BUILDFLAG(IS_ANDROID) + ai_chat::HandleShowSoftKeyboard(web_contents()); +#endif +} + void AIChatUIPageHandler::GetConversationHistory( GetConversationHistoryCallback callback) { if (!active_chat_tab_helper_) { diff --git a/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.h b/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.h index 60f960360eda..9f86c5edbd78 100644 --- a/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.h +++ b/browser/ui/webui/ai_chat/ai_chat_ui_page_handler.h @@ -64,6 +64,7 @@ class AIChatUIPageHandler : public ai_chat::mojom::PageHandler, mojom::ActionType action_type) override; void SubmitSummarizationRequest() override; void HandleVoiceRecognition() override; + void HandleShowSoftKeyboard() override; void GetConversationHistory(GetConversationHistoryCallback callback) override; void MarkAgreementAccepted() override; void GetSuggestedQuestions(GetSuggestedQuestionsCallback callback) override; diff --git a/components/ai_chat/core/common/mojom/ai_chat.mojom b/components/ai_chat/core/common/mojom/ai_chat.mojom index a71cef6f94f8..68eeb93bc820 100644 --- a/components/ai_chat/core/common/mojom/ai_chat.mojom +++ b/components/ai_chat/core/common/mojom/ai_chat.mojom @@ -246,6 +246,7 @@ interface PageHandler { SubmitHumanConversationEntry(string input); SubmitHumanConversationEntryWithAction(string input, ActionType action_type); HandleVoiceRecognition(); + HandleShowSoftKeyboard(); SubmitSummarizationRequest(); MarkAgreementAccepted(); // Get associated page information. If there is none then |site_info| will be diff --git a/components/ai_chat/resources/page/components/input_box/index.tsx b/components/ai_chat/resources/page/components/input_box/index.tsx index 2f15a1e616e4..0c79a4a01012 100644 --- a/components/ai_chat/resources/page/components/input_box/index.tsx +++ b/components/ai_chat/resources/page/components/input_box/index.tsx @@ -27,6 +27,7 @@ type Props = Pick @@ -41,6 +42,7 @@ function InputBox(props: InputBoxProps) { } const handleSubmit = () => { + querySubmitted.current = true props.context.submitInputTextToAPI() } @@ -76,10 +78,21 @@ function InputBox(props: InputBoxProps) { } } + const querySubmitted = React.useRef(false) + const maybeAutofocus = (node: HTMLTextAreaElement | null) => { - if (node && props.context.selectedActionType) { + if (!node) { + return + } + + if (props.context.selectedActionType) { node.focus() } + if (props.context.isMobile && props.context.hasAcceptedAgreement && + !props.context.hasInitialHistory && !querySubmitted.current) { + node.focus() + getPageHandlerInstance().pageHandler.handleShowSoftKeyboard() + } } return ( diff --git a/components/ai_chat/resources/page/state/context.ts b/components/ai_chat/resources/page/state/context.ts index cf46632e2e1c..4140a481b844 100644 --- a/components/ai_chat/resources/page/state/context.ts +++ b/components/ai_chat/resources/page/state/context.ts @@ -40,6 +40,7 @@ export interface AIChatContext extends CharCountContext { showAgreementModal: boolean shouldSendPageContents: boolean isMobile: boolean + hasInitialHistory: boolean inputText: string selectedActionType: mojom.ActionType | undefined isToolsMenuOpen: boolean @@ -85,6 +86,7 @@ export const defaultContext: AIChatContext = { showAgreementModal: false, shouldSendPageContents: true, isMobile: false, + hasInitialHistory: false, inputText: '', selectedActionType: undefined, isToolsMenuOpen: false, diff --git a/components/ai_chat/resources/page/state/data-context-provider.tsx b/components/ai_chat/resources/page/state/data-context-provider.tsx index 1737820bd06e..c016bdc591c3 100644 --- a/components/ai_chat/resources/page/state/data-context-provider.tsx +++ b/components/ai_chat/resources/page/state/data-context-provider.tsx @@ -336,6 +336,8 @@ function DataContextProvider(props: DataContextProviderProps) { } const isMobile = React.useMemo(() => loadTimeData.getBoolean('isMobile'), []) + const hasInitialHistory = React.useMemo(() => + loadTimeData.getBoolean('hasInitialHistory'), []) React.useEffect(() => { initialiseForTargetTab() @@ -405,6 +407,7 @@ function DataContextProvider(props: DataContextProviderProps) { showAgreementModal, shouldSendPageContents: shouldSendPageContents && siteInfo?.isContentAssociationPossible, isMobile, + hasInitialHistory, inputText, isCharLimitExceeded, isCharLimitApproaching, diff --git a/components/ai_rewriter/resources/page/components/BeginGeneration.tsx b/components/ai_rewriter/resources/page/components/BeginGeneration.tsx index 480a64b196f0..8dcdade0eec8 100644 --- a/components/ai_rewriter/resources/page/components/BeginGeneration.tsx +++ b/components/ai_rewriter/resources/page/components/BeginGeneration.tsx @@ -45,6 +45,7 @@ export default function BeginGeneration() { isCharLimitExceeded: context.isCharLimitExceeded, shouldDisableUserInput: context.isGenerating, isMobile: false, + hasInitialHistory: false, hasAcceptedAgreement: true }} />