Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autofocuses input box on Android Leo chat window on a fresh chat #25512

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

SergeyZhukovsky
Copy link
Member

@SergeyZhukovsky SergeyZhukovsky commented Sep 10, 2024

Resolves brave/brave-browser#40968

Submitter Checklist:

  • I confirm that no security/privacy review is needed and no other type of reviews are needed, or that I have requested them
  • There is a ticket for my issue
  • Used Github auto-closing keywords in the PR description above
  • Wrote a good PR/commit description
  • Squashed any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
  • Added appropriate labels (QA/Yes or QA/No; release-notes/include or release-notes/exclude; OS/...) to the associated issue
  • Checked the PR locally:
    • npm run test -- brave_browser_tests, npm run test -- brave_unit_tests wiki
    • npm run presubmit wiki, npm run gn_check, npm run tslint
  • Ran git rebase master (if needed)

Reviewer Checklist:

  • A security review is not needed, or a link to one is included in the PR description
  • New files have MPL-2.0 license header
  • Adequate test coverage exists to prevent regressions
  • Major classes, functions and non-trivial code blocks are well-commented
  • Changes in component dependencies are properly reflected in gn
  • Code follows the style guide
  • Test plan is specified in PR before merging

After-merge Checklist:

Test Plan:

Screen_recording_20240910_154410.webm

the test plan is specified in the issue

@SergeyZhukovsky SergeyZhukovsky self-assigned this Sep 10, 2024
@github-actions github-actions bot added the CI/storybook-url Deploy storybook and provide a unique URL for each build label Sep 10, 2024
@SergeyZhukovsky SergeyZhukovsky force-pushed the android_leo_input_box_focus branch 2 times, most recently from 9151f4f to 00844ae Compare September 10, 2024 22:15
@brave-builds
Copy link
Collaborator

A Storybook has been deployed to preview UI for the latest push

@brave-builds
Copy link
Collaborator

A Storybook has been deployed to preview UI for the latest push

@brave-builds
Copy link
Collaborator

A Storybook has been deployed to preview UI for the latest push

Copy link
Contributor

@fallaciousreasoning fallaciousreasoning left a comment

Choose a reason for hiding this comment

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

Mostly looks great with a few comments 😄

@@ -76,10 +78,21 @@ function InputBox(props: InputBoxProps) {
}
}

const querySubmitted = React.useRef(false)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: not strictly required but I'd prefer this to be defined before its used (i.e. before handleSubmit)

if (props.context.isMobile && props.context.hasAcceptedAgreement &&
!props.context.hasInitialHistory && !querySubmitted.current) {
node.focus()
getPageHandlerInstance().pageHandler.handleShowSoftKeyboard()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should go through the context so we can bind the input_box to another mojom interface (which I think we do for the AI Rewriter stuff, which currently isn't active)

@@ -336,6 +336,8 @@ function DataContextProvider(props: DataContextProviderProps) {
}

const isMobile = React.useMemo(() => loadTimeData.getBoolean('isMobile'), [])
const hasInitialHistory = React.useMemo(() =>
Copy link
Contributor

Choose a reason for hiding this comment

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

I know you're just following what this file does but I don't think there's any point to this being in a useMemo as:

  1. It isn't an expensive calculation (so we could just store it in a normal variable)
  2. It will never change

Personally, I'd probably store the variable outside the component as

const hasInitialHistory = loadTimeData.getBoolean('hasInitialHistory')
function DataContextProvider(...) {
    ...
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Really this shouldn't be part of the context at all as its completely static data, but I guess its fine as part of the interface the input_box expects.

@SergeyZhukovsky SergeyZhukovsky force-pushed the android_leo_input_box_focus branch from 6932add to 4756a38 Compare September 11, 2024 13:48
@SergeyZhukovsky
Copy link
Member Author

@fallaciousreasoning the review suggestions are addressed.

Comment on lines 97 to 106
content::WebContents* chat_web_contents = GetChatWebContents();
size_t visible_conversation_history_size = 0;
if (chat_web_contents) {
ai_chat::AIChatTabHelper* active_chat_tab_helper =
ai_chat::AIChatTabHelper::FromWebContents(chat_web_contents);
visible_conversation_history_size =
active_chat_tab_helper->GetVisibleConversationHistory().size();
}
untrusted_source->AddBoolean("hasInitialHistory",
visible_conversation_history_size != 0);
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this needs to be provided by the C++. The context can just check if the first call to get conversation history has any data.

Copy link
Member Author

Choose a reason for hiding this comment

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

@petemill what particular call do you mean?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think @petemill means here where we call getConversationHistory

If its the first call, and we get nothing back, we don't have initial history.

Copy link
Member Author

Choose a reason for hiding this comment

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

@fallaciousreasoning the getConversationHistory returns 0 always even if we have history.

Copy link
Contributor

Choose a reason for hiding this comment

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

Surely there's some way that we load the active conversation history because we display it, right?

node.focus()
}
if (props.context.isMobile && props.context.hasAcceptedAgreement &&
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking this should be done in a higher level because input is shared with ai rewriter dialog - it doesn't need to be in a conversation with history and doesn't need to have conversation awareness. We can also remove the props.context.isMobile

Copy link
Member Author

@SergeyZhukovsky SergeyZhukovsky Sep 12, 2024

Choose a reason for hiding this comment

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

I can move the check on the higher level but node.focus would be called here depending of true false the higher check function return. The rewriter could just have have a stub with false. Does the rewriter need to be aware of hasAcceptedAgreement as it was there before?

Copy link
Member Author

Choose a reason for hiding this comment

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

I moved it to the higher level

Copy link
Member

@petemill petemill left a comment

Choose a reason for hiding this comment

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

Is focus not enugh to show the virtual keyboard? If this is in a regular Tab instead of a custom popup, would we still require a custom HandleShowSoftKeyboard function? I'm asking because wondering if it's worth the effort due to us soon removing the custom popup in favor of a Tab.

@SergeyZhukovsky
Copy link
Member Author

SergeyZhukovsky commented Sep 12, 2024

Is focus not enugh to show the virtual keyboard? If this is in a regular Tab instead of a custom popup, would we still require a custom HandleShowSoftKeyboard function? I'm asking because wondering if it's worth the effort due to us soon removing the custom popup in favor of a Tab.

I've been fighting with it and it doesn't pop up when I programmatically call focus. The conclusion from other users who tried that was something like that it's generally not possible to programmatically bring up the soft keyboard without some form of user interaction. This is a security feature and accessibility consideration in many mobile operating systems. It's worth effort as we want the soft keyboard to popup regardless are we in a separate window or inside a tab.

@SergeyZhukovsky SergeyZhukovsky force-pushed the android_leo_input_box_focus branch 2 times, most recently from d81551d to 42164ae Compare September 12, 2024 18:22
@SergeyZhukovsky
Copy link
Member Author

I've discussed it with @petemill in slack and we decided to wait with that PR till his is merged #24921

Copy link
Contributor

@fallaciousreasoning fallaciousreasoning left a comment

Choose a reason for hiding this comment

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

lgtm % nits

| 'shouldDisableUserInput'
| 'hasAcceptedAgreement'>

interface InputBoxProps {
context: Props
onFocusInputMobile?: () => unknown
maybeShowSoftKeyboard?: (querySubmitted: boolean) => unknown
Copy link
Contributor

Choose a reason for hiding this comment

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

hey sorry, I know you're just cargo culting here but I think these methods should return void rather than unknown.

Even if we do return something from these functions its fine, it just means the InputBox doesn't do anything with the return type.

For example, the below is fine:

let foo: ((n: number) => void) = n => n * n

see playground:
https://www.typescriptlang.org/play/?ssl=5&ssc=28&pln=5&pc=29#code/C4TwDgpgBAUghiAzgMQK4DsDGUC8UAU6AXFOqgLYBGEATgJS4B8UAbgPYCWAJgFB8A2EYFABmbNiXhI0WXAXQMczdD0HDKcGpIQoM2PIUXKoAKlJA

@@ -63,6 +63,7 @@ export interface AIChatContext extends CharCountContext {
resetSelectedActionType: () => void
handleActionTypeClick: (actionType: mojom.ActionType) => void
setIsToolsMenuOpen: (isOpen: boolean) => void
handleShowSoftKeyboard?: () => unknown
Copy link
Contributor

Choose a reason for hiding this comment

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

same here:

Suggested change
handleShowSoftKeyboard?: () => unknown
handleShowSoftKeyboard?: () => void

}

function InputBox(props: InputBoxProps) {
const onInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
props.context.setInputText(e.target.value)
}

const querySubmitted = React.useRef(false)
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: I'm pretty sure we could use useState here instead of useRef (though not 100%).

@@ -78,21 +80,16 @@ function InputBox(props: InputBoxProps) {
}
}

const querySubmitted = React.useRef(false)

const maybeAutofocus = (node: HTMLTextAreaElement | null) => {
if (!node) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit curious about how often this gets called - is it every render, or is it only when the ref (node) is changed?

Comment on lines 97 to 106
content::WebContents* chat_web_contents = GetChatWebContents();
size_t visible_conversation_history_size = 0;
if (chat_web_contents) {
ai_chat::AIChatTabHelper* active_chat_tab_helper =
ai_chat::AIChatTabHelper::FromWebContents(chat_web_contents);
visible_conversation_history_size =
active_chat_tab_helper->GetVisibleConversationHistory().size();
}
untrusted_source->AddBoolean("hasInitialHistory",
visible_conversation_history_size != 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think @petemill means here where we call getConversationHistory

If its the first call, and we get nothing back, we don't have initial history.

@SergeyZhukovsky SergeyZhukovsky force-pushed the android_leo_input_box_focus branch from 42164ae to 07cab19 Compare October 2, 2024 18:53
@SergeyZhukovsky SergeyZhukovsky force-pushed the android_leo_input_box_focus branch 2 times, most recently from b954367 to 39a8cdd Compare October 3, 2024 14:50
@brave-builds
Copy link
Collaborator

A Storybook has been deployed to preview UI for the latest push

@thypon thypon removed their assignment Oct 3, 2024
@SergeyZhukovsky SergeyZhukovsky force-pushed the android_leo_input_box_focus branch from 39a8cdd to 0cdd853 Compare January 17, 2025 18:35
@SergeyZhukovsky SergeyZhukovsky force-pushed the android_leo_input_box_focus branch from 0cdd853 to 36292c7 Compare January 17, 2025 21:33
@brave-builds
Copy link
Collaborator

A Storybook has been deployed to preview UI for the latest push

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI/storybook-url Deploy storybook and provide a unique URL for each build
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Focus input box control on a new conversation on Leo on Android
5 participants