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

[$250] [Search v2.2] - Chats - Selected chat is hidden when navigating via keyboard #48902

Open
2 of 6 tasks
IuliiaHerets opened this issue Sep 10, 2024 · 31 comments
Open
2 of 6 tasks
Assignees
Labels
Bug Something is broken. Auto assigns a BugZero manager. Daily KSv2 Engineering External Added to denote the issue can be worked on by a contributor Help Wanted Apply this label when an issue is open to proposals by contributors Overdue

Comments

@IuliiaHerets
Copy link

IuliiaHerets commented Sep 10, 2024

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: 9.0.31-14
Reproducible in staging?: Y
Reproducible in production?: N/A
Email or phone of affected tester (no customers): [email protected]
Issue reported by: Applause Internal Team

Action Performed:

Precondition:

  • Account has many chats.
  1. Go to staging.new.expensify.com
  2. Go to Search > Chats.
  3. Navigate to the bottom of the list via keyboard up button.

Expected Result:

The selected chat will be not hidden when navigating via keyboard.

Actual Result:

The selected chat is hidden when navigating via keyboard.
This issue happens with the chat at the bottom of the list.

Workaround:

Unknown

Platforms:

  • Android: Native
  • Android: mWeb Chrome
  • iOS: Native
  • iOS: mWeb Safari
  • MacOS: Chrome / Safari
  • MacOS: Desktop

Screenshots/Videos

Bug6598844_1725981162147.20240910_230910.mp4

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~021833559992291211111
  • Upwork Job ID: 1833559992291211111
  • Last Price Increase: 2024-10-01
Issue OwnerCurrent Issue Owner: @rayane-djouah
@IuliiaHerets IuliiaHerets added DeployBlockerCash This issue or pull request should block deployment Bug Something is broken. Auto assigns a BugZero manager. labels Sep 10, 2024
Copy link

melvin-bot bot commented Sep 10, 2024

Triggered auto assignment to @marcochavezf (DeployBlockerCash), see https://stackoverflowteams.com/c/expensify/questions/9980/ for more details.

Copy link

melvin-bot bot commented Sep 10, 2024

Triggered auto assignment to @isabelastisser (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details. Please add this bug to a GH project, as outlined in the SO.

@melvin-bot melvin-bot bot added the Daily KSv2 label Sep 10, 2024
@github-actions github-actions bot added Engineering Hourly KSv2 and removed Daily KSv2 labels Sep 10, 2024
Copy link
Contributor

👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

  1. Identify the pull request that introduced this issue and revert it.
  2. Find someone who can quickly fix the issue.
  3. Fix the issue yourself.

@marcochavezf
Copy link
Contributor

I don't see the Chats section in Search tab in production:

Screenshot 2024-09-10 at 11 32 05 a m

Seems this is a new feature and I don't think it should block the deploy

@marcochavezf marcochavezf added External Added to denote the issue can be worked on by a contributor Daily KSv2 and removed DeployBlockerCash This issue or pull request should block deployment Hourly KSv2 labels Sep 10, 2024
Copy link

melvin-bot bot commented Sep 10, 2024

Job added to Upwork: https://www.upwork.com/jobs/~021833559992291211111

@melvin-bot melvin-bot bot changed the title Search - Chats - Selected chat is hidden when navigating via keyboard [$250] Search - Chats - Selected chat is hidden when navigating via keyboard Sep 10, 2024
@melvin-bot melvin-bot bot added the Help Wanted Apply this label when an issue is open to proposals by contributors label Sep 10, 2024
Copy link

melvin-bot bot commented Sep 10, 2024

Triggered auto assignment to Contributor-plus team member for initial proposal review - @rayane-djouah (External)

@Nodebrute
Copy link
Contributor

@marcochavezf I am unable to reproduce this issue on latest main

Screen.Recording.2024-09-10.at.10.59.35.PM.mov

@Amoralchik
Copy link
Contributor

Amoralchik commented Sep 10, 2024

Edited by proposal-police: This proposal was edited at 2024-09-10 21:07:46 UTC.

Proposal

Please re-state the problem that we are trying to solve in this issue.

The element selected using the keyboard is outside the visible area.

What is the root cause of that problem?

In useArrowKeyFocusManager, for the up and down arrows, we call the scrollToIndex function within onFocusedIndexChange, which scrolls to the selected element.

const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({
initialFocusedIndex: flattenedSections.allOptions.findIndex((option) => option.keyForList === initiallyFocusedOptionKey),
maxIndex: Math.min(flattenedSections.allOptions.length - 1, CONST.MAX_SELECTION_LIST_PAGE_LENGTH * currentPage - 1),
disabledIndexes: disabledArrowKeyIndexes,
isActive: isFocused,
onFocusedIndexChange: (index: number) => {
scrollToIndex(index, true);
},
isFocused,
});

scrollToLocation isn't functioning correctly due to variations in element sizes, caused by the offset in getItemLayout not accurately reflecting the distance between elements. This is due to the offset being set here:

if (SearchUtils.isTransactionListItemType(item) || SearchUtils.isReportActionListItemType(item)) {
return isLargeScreenWidth ? variables.optionRowHeight + listItemPadding : transactionItemMobileHeight + listItemPadding;
}

to be the same as for Transactions.

What changes do you think we should make in order to solve the problem?

First, I would remove scrollToIndex from onFocusedIndexChange since I don’t see the point in keeping it (see optional).

We can set a fixed height for all elements or somehow determine the height of the component and store this data in reportAction. Then, we can retrieve it in getItemHeight.

proof of concept:

2024-09-11.18-55-07.mp4

What alternative solutions did you explore? (Optional)

I propose removing scrollToIndex from onFocusedIndexChange, as it only handles scrolling, and without it, I noticed everything works correctly.

code after:

    const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({
        initialFocusedIndex: flattenedSections.allOptions.findIndex((option) => option.keyForList === initiallyFocusedOptionKey),
        maxIndex: Math.min(flattenedSections.allOptions.length - 1, CONST.MAX_SELECTION_LIST_PAGE_LENGTH * currentPage - 1),
        disabledIndexes: disabledArrowKeyIndexes,
        isActive: isFocused,
        onFocusedIndexChange: () => {},
        isFocused,
    });

proof of concept:

2024-09-10.22-55-20.mp4

@1subodhpathak
Copy link
Contributor

I am able to reproduce this problem!

Recording.2024-09-11.195526.mp4

But observed one thing, it only happens when the chat size greater than initialy defined is selected
image
See this

@Amoralchik
Copy link
Contributor

Update Proposal

@isabelastisser
Copy link
Contributor

isabelastisser commented Sep 11, 2024

@rayane-djouah, are you still able to reproduce this? Thanks!

@rayane-djouah
Copy link
Contributor

I'm able to reproduce

Screen.Recording.2024-09-11.at.11.48.46.PM.mov

Copy link

melvin-bot bot commented Sep 17, 2024

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

@melvin-bot melvin-bot bot added the Overdue label Sep 17, 2024
@rayane-djouah
Copy link
Contributor

Reviewing now 👀

@melvin-bot melvin-bot bot removed the Overdue label Sep 17, 2024
@1subodhpathak
Copy link
Contributor

Proposal

Please re-state the problem that we are trying to solve in this issue.

The issue occurs when the user navigates to the bottom of the chat list using the keyboard, the selected chat becomes hidden instead of staying visible

What is the root cause of that problem?

The root cause of this problem is related to the focus and scrolling behavior when using keyboard navigation in the chat list.

  • The useArrowKeyFocusManager hook, which handles keyboard navigation, does not ensure the focused chat item remains visible when the user navigates to the bottom.

  • The scrollToIndex method in BaseSelectionList.tsx may not be properly scrolling to the last item in the list when it is selected.

What changes do you think we should make in order to solve the problem?

  1. update the setFocusedIndex function in arrowUpCallback and arrowDownCallback to ensure that when the focused index changes, the list scrolls to bring the selected chat into view.

    const arrowUpCallback = useCallback(() => {
    if (maxIndex < 0 || !isFocused) {
    return;
    }

  2. Update the scrollToIndex function to ensure that when navigating to the last item in the list, the selected item is scrolled into view and remains visible.

    const scrollToIndex = useCallback(
    (index: number, animated = true) => {
    const item = flattenedSections.allOptions[index];
    if (!listRef.current || !item) {
    return;
    }
    const itemIndex = item.index ?? -1;
    const sectionIndex = item.sectionIndex ?? -1;
    listRef.current.scrollToLocation({sectionIndex, itemIndex, animated, viewOffset: variables.contentHeaderHeight});
    },
    // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
    [flattenedSections.allOptions],
    );

  3. onFocusedIndexChange passed to useArrowKeyFocusManager triggers the scrolling behavior in BaseSelectionList when the selected chat item changes.

    const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({
    initialFocusedIndex: flattenedSections.allOptions.findIndex((option) => option.keyForList === initiallyFocusedOptionKey),
    maxIndex: Math.min(flattenedSections.allOptions.length - 1, CONST.MAX_SELECTION_LIST_PAGE_LENGTH * currentPage - 1),
    disabledIndexes: disabledArrowKeyIndexes,
    isActive: isFocused,
    onFocusedIndexChange: (index: number) => {
    scrollToIndex(index, true);
    },
    isFocused,
    });

  4. the layout is properly calculated so that the selected chat remains in view when navigating to the bottom of the list.

const getItemLayout = (data, index) => ({
    length: ITEM_HEIGHT, 
    offset: ITEM_HEIGHT * index,
    index,
}); 

What alternative solutions did you explore? (Optional)

An alternative approach could involve increasing the render window size of the list to ensure that more items are rendered and remain visible as the user navigates with the keyboard. However, this could lead to performance issues for long lists and is less optimal than ensuring the focus and scroll behavior work correctly.

@luacmartins luacmartins changed the title [$250] Search - Chats - Selected chat is hidden when navigating via keyboard [$250] [Search v2.2] - Chats - Selected chat is hidden when navigating via keyboard Sep 19, 2024
@isabelastisser
Copy link
Contributor

@rayane-djouah, can you please provide an update? Did you review all the proposals? Thanks!

@rayane-djouah
Copy link
Contributor

@Amoralchik Thanks for the proposal.

It appears the main issue is that auto-scrolling to the active list element isn't functioning correctly due to the variations in list element height, which is not accurately reflected in the calculations.

Is the problem linked to both the getItemHeight function and the getItemLayout?

Regarding the proposed solution, setting a fixed height might not be ideal as it breaks the content, We need to ensure the full content of report actions is displayed.

How might we dynamically determine the height of ReportActionListItemType items?

Regarding your optional solution: removing scrollToIndex causes a regression:

Staging Removing scrollToIndex
Screen.Recording.2024-09-20.at.12.16.27.PM.mov
Screen.Recording.2024-09-20.at.12.15.47.PM.mov

@rayane-djouah
Copy link
Contributor

@1subodhpathak I don't see a difference between your proposal and the later one. Additionally, please refrain from using overly wordy explanations; the changes you intend to make to fix the bug are not clear. Make sure to follow the guidelines in CONTRIBUTING.md when submitting proposals.

@1subodhpathak
Copy link
Contributor

Okay! marked your words @rayane-djouah
Thanks for the review

@Amoralchik
Copy link
Contributor

@rayane-djouah Hi,

How might we dynamically determine the height of ReportActionListItemType items? - I think (though I'm not sure, this is just a hypothesis) we could try creating a function that splits the text into Markdown from the HTML value, then gathers all the attributes and adds them to the block size.

To make it clearer, here’s an example of what the code might look like:

function calculateHeight(inputString) {
  const lines = inputString.split('\n'); // or <br/>
  let height = 0;
  lines.forEach(line => {
    if (line.startsWith('<h1>')) {
      height += variables.h1; //
    } 
    // ......
    else {
      height += variables.span;
    }
  });

  return height;
}

I will try this approach, and if everything works well, I will update the proposal by/on Monday.

Is the problem linked to both the getItemHeight function and the getItemLayout? - Yes
Here:

const fullItemHeight = getItemHeight(item);
itemLayouts.push({length: fullItemHeight, offset});
offset += fullItemHeight;

and then here:
const getItemLayout = (data: Array<SectionListData<TItem, SectionWithIndexOffset<TItem>>> | null, flatDataArrayIndex: number) => {
const targetItem = flattenedSections.itemLayouts[flatDataArrayIndex];

Regarding your optional solution: removing scrollToIndex causes a regression - Oh I'm sorry, yeah that makes sense

@rayane-djouah
Copy link
Contributor

@rayane-djouah, can you please provide an update? Did you review all the proposals? Thanks!

@isabelastisser We don't have a satisfactory proposal yet.

@rayane-djouah
Copy link
Contributor

Waiting on proposals

Copy link

melvin-bot bot commented Sep 24, 2024

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

Copy link

melvin-bot bot commented Sep 24, 2024

@marcochavezf @isabelastisser @rayane-djouah this issue was created 2 weeks ago. Are we close to approving a proposal? If not, what's blocking us from getting this issue assigned? Don't hesitate to create a thread in #expensify-open-source to align faster in real time. Thanks!

@rayane-djouah
Copy link
Contributor

Waiting on proposals

Copy link

melvin-bot bot commented Sep 30, 2024

@marcochavezf, @isabelastisser, @rayane-djouah Huh... This is 4 days overdue. Who can take care of this?

@melvin-bot melvin-bot bot added the Overdue label Sep 30, 2024
@rayane-djouah
Copy link
Contributor

Waiting on proposals

@melvin-bot melvin-bot bot removed the Overdue label Sep 30, 2024
Copy link

melvin-bot bot commented Oct 1, 2024

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

@rayane-djouah
Copy link
Contributor

@isabelastisser Do we need to adjust the bounty for this issue to get more proposals?

@isabelastisser
Copy link
Contributor

@rayane-djouah, we're keeping the original amount. Thanks!

@tushar-a-b
Copy link
Contributor

Proposal

Please re-state the problem that we are trying to solve in this issue.

Selected chat is hidden when navigating via keyboard

What is the root cause of that problem?

We are setting static fullItemHeight of 76 for every item in the itemLayouts of flattenedSections list here:
(you can try console logging flattenedSections and look for itemLayouts you will notice every item has 76 height even if it large in size.)

const fullItemHeight = getItemHeight(item);

const getItemHeight = useCallback(
(item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => {
if (SearchUtils.isTransactionListItemType(item) || SearchUtils.isReportActionListItemType(item)) {
return isLargeScreenWidth ? variables.optionRowHeight + listItemPadding : transactionItemMobileHeight + listItemPadding;
}
if (item.transactions.length === 0) {
return 0;
}
if (item.transactions.length === 1) {
return isLargeScreenWidth ? variables.optionRowHeight + listItemPadding : transactionItemMobileHeight + listItemPadding;
}
const baseReportItemHeight = isLargeScreenWidth ? 72 : 108;
return baseReportItemHeight + item.transactions.length * reportItemTransactionHeight + listItemPadding;
},
[isLargeScreenWidth],
);

We have incorrect item heights in the list with the same value of 76 for every item. When an item in the list is larger than 76 then only in that scenario we encounter this issue since it scrolls by considering every item's height as 76 and hence sometimes the selected item goes out of the view.

What changes do you think we should make in order to solve the problem?

We should find out every item's original height and set the fullItemHeight while calculating itemLayouts in flattenedSections below:

const fullItemHeight = getItemHeight(item);

To get the original height of each item we should first wrap the ListItem of renderItem around View component so that we can pass onLayout function to get the height of the item as below:

const renderItem = ({item, index, section}: SectionListRenderItemInfo<TItem, SectionWithIndexOffset<TItem>>) => {
const normalizedIndex = index + (section?.indexOffset ?? 0);
const isDisabled = !!section.isDisabled || item.isDisabled;
const isItemFocused = (!isDisabled || item.isSelected) && (focusedIndex === normalizedIndex || itemsToHighlight?.has(item.keyForList ?? ''));
// We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade.
const showTooltip = shouldShowTooltips && normalizedIndex < 10;
const handleOnCheckboxPress = () => {
if (SearchUtils.isReportListItemType(item)) {
return onCheckboxPress;
}
return onCheckboxPress ? () => onCheckboxPress(item) : undefined;
};
return (
<>
<ListItem
item={item}
isFocused={isItemFocused}
isDisabled={isDisabled}
showTooltip={showTooltip}
canSelectMultiple={canSelectMultiple}
onLongPressRow={onLongPressRow}
onSelectRow={() => {
if (shouldSingleExecuteRowSelect) {
singleExecution(() => selectRow(item, index))();
} else {
selectRow(item, index);
}
}}
onCheckboxPress={handleOnCheckboxPress()}
onDismissError={() => onDismissError?.(item)}
shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow}
// We're already handling the Enter key press in the useKeyboardShortcut hook, so we don't want the list item to submit the form
shouldPreventEnterKeySubmit
rightHandSideComponent={rightHandSideComponent}
keyForList={item.keyForList ?? ''}
isMultilineSupported={isRowMultilineSupported}
isAlternateTextMultilineSupported={isAlternateTextMultilineSupported}
alternateTextNumberOfLines={alternateTextNumberOfLines}
onFocus={() => {
if (shouldIgnoreFocus || isDisabled) {
return;
}
setFocusedIndex(normalizedIndex);
}}
shouldSyncFocus={!isTextInputFocusedRef.current}
/>
{item.footerContent && item.footerContent}
</>
);
};

to

<View onLayout={onItemLayout}>
       <ListItem 
        .........
        .........
        .........
        />
</View>

where in onItemLayout function we will track the height of every rendered item in itemHeights state so that we can use it later:

const [itemHeights, setItemHeights] = useState<{ [key: number]: number }>({});
const onItemLayout = useCallback((event: LayoutChangeEvent, itemIndex: number) => {
        const { height } = event.nativeEvent.layout;
        setItemHeights(prevHeights => ({
            ...prevHeights,
            [itemIndex]: height,
        }));
    }, []);

Now we will use this itemHeights to set the fullItemHeight in flattenedSections so that we will get correct height for every item in itemLayouts here:

const fullItemHeight = getItemHeight(item);

to

const fullItemHeight = itemHeights[optionIndex] ?? getItemHeight(item);

We will also update the dependency of flattenedSections and will add itemHeights to it.

const flattenedSections = useMemo<FlattenedSectionsReturn<TItem>>(() => {
...
}, [canSelectMultiple, sections, customListHeader, customListHeaderHeight, getItemHeight, itemHeights]);

Result (refer video below):

result.mp4

What alternative solutions did you explore? (Optional)

@melvin-bot melvin-bot bot added the Overdue label Oct 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something is broken. Auto assigns a BugZero manager. Daily KSv2 Engineering External Added to denote the issue can be worked on by a contributor Help Wanted Apply this label when an issue is open to proposals by contributors Overdue
Projects
Status: Polish
Development

No branches or pull requests

8 participants