-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[WIP] feat: Open report with linked/unread report actions in the middle of the chat #90375
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
Closed
chrispader
wants to merge
52
commits into
Expensify:main
from
margelo:@chrispader/feat/open-linked-report-action-in-the-middle-of-chat
Closed
Changes from all commits
Commits
Show all changes
52 commits
Select commit
Hold shift + click to select a range
399267c
feat: use `initialScrollIndex`, anchor target report action in the mi…
chrispader a7b13ff
feat: more sophisticated center estimation/calculation
chrispader 1d38351
feat: render in middle with skeleton
chrispader 162a6ca
fix: don't estimate item height and link to top edge of item
chrispader 8491644
feat: measure linked item and readjust
chrispader 2234980
fix: report reloading when message marked unread
chrispader 8796840
Merge branch 'main' into @chrispader/feat/open-linked-report-action-i…
chrispader 964537c
fix: types
chrispader 21104aa
fix: invalid import
chrispader e6c2746
Merge branch 'main' into @chrispader/feat/open-linked-report-action-i…
chrispader c1e717a
fix: show skeleton until all report actions are loaded
chrispader 7963fa2
fix: also manually scroll for unread message
chrispader 1a74492
refactor: extract vertical alignment logic into hook
chrispader 1915f6a
fix: remove duplicate code
chrispader 74e9e64
refactor: re-arrange changes to avoid big Git diff
chrispader 397c7b2
fix: remove duplicate return values
chrispader a7c7ba9
fix: invalid hook import name
chrispader 8d5d951
revert: unnecessary `listID` change
chrispader a03e362
test: add unit test for `useVerticallyCenteredInitialContent` hook
chrispader 28c28cd
fix: skeleton showing when message marked unread
chrispader 276217c
fix: dep array
chrispader 9debe7c
test: add another unit test
chrispader b3ef35a
fix: upper edge centered
chrispader 1228e7e
fix: prevent scroll when message is marked unread
chrispader 7869832
fix: popover flashing when message marked unread
chrispader 43a4178
fix: lint changes
chrispader 78687e2
fix: info badge doesn't properly link
chrispader 3844133
fix: vertically center edge/center based on list height
chrispader ea56f74
fix: spell check
chrispader b354c37
fix: prefer early return
chrispader 31e555d
fix: deps
chrispader d87e092
refactor: extract logic into multiple files and simplify code
chrispader d3b7e1f
temp: rename file for Git
chrispader 1b53084
temp: rename file for Git
chrispader c819596
refactor: rename hook
chrispader 884269b
fix: invalid import
chrispader 1dd642d
refactor: remove unused code around `useFlashListScrollKey`
chrispader 5835e1b
fix: remove unused handling of `GetOlderActions` call
chrispader d64e117
fix: reduce `MEASURED_SCROLL_FALLBACK_MS` to 1 sec
chrispader fdf02dc
refactor: remove more unused code
chrispader 4ca212f
fix: typecheck
chrispader fc61958
Merge branch 'main' into @chrispader/feat/open-linked-report-action-i…
chrispader 310fd76
fix: typo
chrispader c2170c8
Merge branch 'main' into @chrispader/feat/open-linked-report-action-i…
chrispader d207913
revert: unrelated changes
chrispader 615a57a
revert: unrelated changes
chrispader 32da57a
docs: add comment for `eslint-disable no-param-reassign`
chrispader 0ceb25f
fix: cancel `requestAnimationFrame` callbacks
chrispader a4d80df
fix: increase `MEASURED_SCROLL_FALLBACK_MS` to 3 seconds
chrispader 528574a
Merge branch 'main' into @chrispader/feat/open-linked-report-action-i…
chrispader 46e93ac
fix: wait for measurement + scroll before skeleton is hidden
chrispader 4eb6b75
fix: absolutely center item
chrispader File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import CONST from '@src/CONST'; | ||
|
|
||
| const INITIAL_TARGET_REPORT_ACTION_ESTIMATED_HEIGHT = CONST.CHAT_SKELETON_VIEW.AVERAGE_ROW_HEIGHT; | ||
| const INITIAL_VIEWPORT_OVERSCAN_ITEMS = 2; | ||
|
|
||
| type InitialViewportRange = { | ||
| first: number; | ||
| last: number; | ||
| requiredMountedItems: number; | ||
| }; | ||
|
|
||
| type InitialViewportResetSession = { | ||
| listID: string; | ||
| reportID: string; | ||
| linkedReportActionID: string | undefined; | ||
| initialScrollKey: string | undefined; | ||
| }; | ||
|
|
||
| type MeasuredLinkedRowScrollPosition = { | ||
| viewOffset?: number; | ||
| viewPosition?: number; | ||
| }; | ||
|
|
||
| function isUnreadMarkerOnlyInitialScrollKeyChange( | ||
| previousSession: InitialViewportResetSession | undefined, | ||
| listID: string, | ||
| reportID: string, | ||
| linkedReportActionID: string | undefined, | ||
| initialScrollKey: string | undefined, | ||
| ) { | ||
| if (!previousSession || linkedReportActionID) { | ||
| return false; | ||
| } | ||
|
|
||
| const didUnreadMarkerChange = previousSession.initialScrollKey !== initialScrollKey; | ||
| const isSameListSession = previousSession.listID === listID && previousSession.reportID === reportID && previousSession.linkedReportActionID === linkedReportActionID; | ||
|
|
||
| return didUnreadMarkerChange && isSameListSession; | ||
| } | ||
|
|
||
| function isInitialViewportCovered(mountedIndices: Set<number>, range: InitialViewportRange, initialScrollIndex: number) { | ||
| if (mountedIndices.size < range.requiredMountedItems) { | ||
| return false; | ||
| } | ||
|
|
||
| const mountedIndexList = Array.from(mountedIndices); | ||
| const hasItemBeforeInitialTarget = range.first >= initialScrollIndex || mountedIndexList.some((index) => index < initialScrollIndex); | ||
| const hasItemAfterInitialTarget = range.last <= initialScrollIndex || mountedIndexList.some((index) => index > initialScrollIndex); | ||
|
|
||
| return hasItemBeforeInitialTarget && hasItemAfterInitialTarget; | ||
| } | ||
|
|
||
| /** | ||
| * Inverted FlashList `scrollToIndex` with `-listHeight / 2` places the row's bottom edge at mid-viewport. | ||
| * Adjust from that baseline to land either the row's top edge or vertical center on mid-viewport. | ||
| */ | ||
| function getMeasuredLinkedRowScrollViewOffset(listHeight: number, layoutHeight: number) { | ||
| const midViewportOffset = -listHeight / 2; | ||
|
|
||
| if (layoutHeight > listHeight) { | ||
| return midViewportOffset + layoutHeight; | ||
| } | ||
|
|
||
| return midViewportOffset + layoutHeight / 2; | ||
| } | ||
|
|
||
| function getMeasuredLinkedRowScrollPosition(listHeight: number, layoutHeight: number): MeasuredLinkedRowScrollPosition { | ||
| if (layoutHeight > listHeight) { | ||
| return {viewOffset: getMeasuredLinkedRowScrollViewOffset(listHeight, layoutHeight)}; | ||
| } | ||
|
|
||
| return {viewPosition: 0.5}; | ||
| } | ||
|
|
||
| function computeInitialViewportRange(listHeight: number, initialScrollIndex: number, visibleActionCount: number): InitialViewportRange | undefined { | ||
| if (listHeight <= 0 || initialScrollIndex < 0) { | ||
| return undefined; | ||
| } | ||
|
|
||
| const estimatedVisibleReportActions = Math.max(1, Math.ceil(listHeight / INITIAL_TARGET_REPORT_ACTION_ESTIMATED_HEIGHT)); | ||
| const radius = Math.ceil(estimatedVisibleReportActions / 2) + INITIAL_VIEWPORT_OVERSCAN_ITEMS; | ||
| const first = Math.max(initialScrollIndex - radius, 0); | ||
| const last = Math.min(initialScrollIndex + radius, visibleActionCount - 1); | ||
|
|
||
| return { | ||
| first, | ||
| last, | ||
| requiredMountedItems: Math.min(estimatedVisibleReportActions, last - first + 1), | ||
| }; | ||
| } | ||
|
|
||
| function findInitialScrollIndex<T>(sortedVisibleReportActions: T[], keyExtractor: (item: T) => string, initialScrollKey: string | undefined) { | ||
| if (!initialScrollKey) { | ||
| return -1; | ||
| } | ||
|
|
||
| return sortedVisibleReportActions.findIndex((item) => keyExtractor(item) === initialScrollKey); | ||
| } | ||
|
|
||
| export type {InitialViewportRange, InitialViewportResetSession, MeasuredLinkedRowScrollPosition}; | ||
| export { | ||
| computeInitialViewportRange, | ||
| findInitialScrollIndex, | ||
| getMeasuredLinkedRowScrollPosition, | ||
| getMeasuredLinkedRowScrollViewOffset, | ||
| isInitialViewportCovered, | ||
| isUnreadMarkerOnlyInitialScrollKeyChange, | ||
| }; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gate requires
mountedIndices.size >= requiredMountedItems, whererequiredMountedItemsis inferred from an average row height. For chats with very tall actions near the anchor, FlashList may mount fewer cells than that estimate, soisInitialViewportCoverednever returns true and the full-screen initial skeleton can remain indefinitely. Unlike measured scroll, there is no timeout fallback for this loading gate.Useful? React with 👍 / 👎.