From 24a9fb35aaf8897dd24f0b4b98b23cbbb95e29ea Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 13 Mar 2024 15:20:24 +0100 Subject: [PATCH 001/161] fix: messages content overlap when bottom sheet is shown --- ios/Podfile.lock | 3 + jest/setup.ts | 3 + package-lock.json | 11 + package.json | 1 + src/App.tsx | 4 + .../ActionSheetAwareScrollViewContext.tsx | 209 ++++++++++++ .../ActionSheetKeyboardSpace.tsx | 301 ++++++++++++++++++ .../ActionSheetAwareScrollView/index.ios.tsx | 31 ++ .../ActionSheetAwareScrollView/index.tsx | 31 ++ .../BaseAnchorForAttachmentsOnly.tsx | 6 +- .../AttachmentPicker/index.native.tsx | 3 +- src/components/AttachmentPicker/types.ts | 7 + src/components/ConfirmContent.tsx | 25 +- .../EmojiPicker/EmojiPickerButton.tsx | 63 ++-- .../EmojiPickerMenu/index.native.tsx | 21 +- .../HTMLRenderers/ImageRenderer.tsx | 4 +- .../HTMLRenderers/MentionUserRenderer.tsx | 6 +- .../HTMLRenderers/PreRenderer.tsx | 6 +- .../KeyboardAvoidingView/index.ios.tsx | 2 +- src/components/KeyboardHandlerProvider.tsx | 12 + src/components/PopoverMenu.tsx | 10 +- src/components/PopoverWithMeasuredContent.tsx | 21 +- .../Reactions/AddReactionBubble.tsx | 3 +- .../QuickEmojiReactions/index.native.tsx | 28 +- .../Reactions/QuickEmojiReactions/types.ts | 2 +- .../ReportActionItemEmojiReactions.tsx | 12 + .../ReportActionItem/MoneyRequestAction.tsx | 5 + .../MoneyRequestPreviewContent.tsx | 3 +- .../MoneyRequestPreview/types.ts | 3 + .../ReportActionItem/ReportPreview.tsx | 6 +- .../ReportActionItem/TaskPreview.tsx | 17 +- src/components/ShowContextMenuContext.ts | 4 +- src/components/ThreeDotsMenu/index.tsx | 16 +- src/hooks/useWorkletStateMachine.ts | 170 ++++++++++ .../BaseReportActionContextMenu.tsx | 5 +- .../report/ContextMenu/ContextMenuActions.tsx | 25 +- .../AttachmentPickerWithMenuItems.tsx | 18 +- .../ReportActionCompose.tsx | 29 +- src/pages/home/report/ReportActionItem.tsx | 111 +++++-- src/pages/home/report/ReportActionsList.tsx | 2 + 40 files changed, 1151 insertions(+), 88 deletions(-) create mode 100644 src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx create mode 100644 src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx create mode 100644 src/components/ActionSheetAwareScrollView/index.ios.tsx create mode 100644 src/components/ActionSheetAwareScrollView/index.tsx create mode 100644 src/components/KeyboardHandlerProvider.tsx create mode 100644 src/hooks/useWorkletStateMachine.ts diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1ea6b65a58b7..1cac95e15036 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2137,6 +2137,7 @@ DEPENDENCIES: - "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)" - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-key-command (from `../node_modules/react-native-key-command`) + - react-native-keyboard-controller (from `../node_modules/react-native-keyboard-controller`) - react-native-launch-arguments (from `../node_modules/react-native-launch-arguments`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-pager-view (from `../node_modules/react-native-pager-view`) @@ -2335,6 +2336,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-image-picker" react-native-key-command: :path: "../node_modules/react-native-key-command" + react-native-keyboard-controller: + :path: "../node_modules/react-native-keyboard-controller" react-native-launch-arguments: :path: "../node_modules/react-native-launch-arguments" react-native-netinfo: diff --git a/jest/setup.ts b/jest/setup.ts index 174e59a7e493..107540140f1d 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -50,6 +50,9 @@ jest.mock('react-native-sound', () => { return SoundMock; }); +// eslint-disable-next-line @typescript-eslint/no-unsafe-return +jest.mock('react-native-keyboard-controller', () => require('react-native-keyboard-controller/jest')); + jest.mock('react-native-share', () => ({ default: jest.fn(), })); diff --git a/package-lock.json b/package-lock.json index 8ceeacdbc086..d3bcfea84c51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,6 +98,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", + "react-native-keyboard-controller": "^1.10.4", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -31419,6 +31420,16 @@ "version": "5.0.1", "license": "MIT" }, + "node_modules/react-native-keyboard-controller": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.10.4.tgz", + "integrity": "sha512-PQ3AcKGnacDBeA1zB1y44XLgj0sZd3Py5Kpml412bKgYiM09JgoK7YbJcUxMayTeEGtZ8GTOteevGTbGq1Otrg==", + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-reanimated": ">=2.3.0" + } + }, "node_modules/react-native-launch-arguments": { "version": "4.0.2", "license": "MIT", diff --git a/package.json b/package.json index a8e16bb7baa6..efadc759b85f 100644 --- a/package.json +++ b/package.json @@ -150,6 +150,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", + "react-native-keyboard-controller": "^1.10.4", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", diff --git a/src/App.tsx b/src/App.tsx index 6316fa80fba1..2759913e7207 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import {GestureHandlerRootView} from 'react-native-gesture-handler'; import {PickerStateProvider} from 'react-native-picker-select'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import '../wdyr'; +import * as ActionSheetAwareScrollView from './components/ActionSheetAwareScrollView'; import ActiveElementRoleProvider from './components/ActiveElementRoleProvider'; import ActiveWorkspaceContextProvider from './components/ActiveWorkspace/ActiveWorkspaceProvider'; import ColorSchemeWrapper from './components/ColorSchemeWrapper'; @@ -14,6 +15,7 @@ import CustomStatusBarAndBackgroundContextProvider from './components/CustomStat import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import InitialURLContextProvider from './components/InitialURLContextProvider'; +import KeyboardHandlerProvider from './components/KeyboardHandlerProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; @@ -79,6 +81,8 @@ function App({url}: AppProps) { CustomStatusBarAndBackgroundContextProvider, ActiveElementRoleProvider, ActiveWorkspaceContextProvider, + KeyboardHandlerProvider, + ActionSheetAwareScrollView.ActionSheetAwareScrollViewProvider, ReportIDsContextProvider, PlaybackContextProvider, FullScreenContextProvider, diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx new file mode 100644 index 000000000000..a8dda4adb621 --- /dev/null +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -0,0 +1,209 @@ +import noop from 'lodash/noop'; +import PropTypes from 'prop-types'; +import type {PropsWithChildren} from 'react'; +import React, {createContext, useMemo} from 'react'; +import type {SharedValue} from 'react-native-reanimated'; +import type {ActionWithPayload, State} from '@hooks/useWorkletStateMachine'; +import useWorkletStateMachine from '@hooks/useWorkletStateMachine'; + +type MeasuredElements = { + fy?: number; + popoverHeight?: number; + height?: number; + composerHeight?: number; +}; +type Context = { + currentActionSheetState: SharedValue>; + transitionActionSheetState: (action: ActionWithPayload) => void; + transitionActionSheetStateWorklet: (action: ActionWithPayload) => void; + resetStateMachine: () => void; +}; +const defaultValue: Context = { + currentActionSheetState: { + value: { + previous: { + state: 'idle', + payload: null, + }, + current: { + state: 'idle', + payload: null, + }, + }, + addListener: noop, + removeListener: noop, + modify: noop, + }, + transitionActionSheetState: noop, + transitionActionSheetStateWorklet: noop, + resetStateMachine: noop, +}; + +const ActionSheetAwareScrollViewContext = createContext(defaultValue); + +const Actions = { + OPEN_KEYBOARD: 'KEYBOARD_OPEN', + CLOSE_KEYBOARD: 'CLOSE_KEYBOARD', + OPEN_POPOVER: 'OPEN_POPOVER', + CLOSE_POPOVER: 'CLOSE_POPOVER', + MEASURE_POPOVER: 'MEASURE_POPOVER', + MEASURE_COMPOSER: 'MEASURE_COMPOSER', + POPOVER_ANY_ACTION: 'POPOVER_ANY_ACTION', + OPEN_EMOJI_PICKER_POPOVER: 'OPEN_EMOJI_PICKER_POPOVER', + OPEN_EMOJI_PICKER_POPOVER_STANDALONE: 'OPEN_EMOJI_PICKER_POPOVER_STANDALONE', + CLOSE_EMOJI_PICKER_POPOVER: 'CLOSE_EMOJI_PICKER_POPOVER', + MEASURE_EMOJI_PICKER_POPOVER: 'MEASURE_EMOJI_PICKER_POPOVER', + HIDE_WITHOUT_ANIMATION: 'HIDE_WITHOUT_ANIMATION', + EDIT_REPORT: 'EDIT_REPORT', + SHOW_DELETE_CONFIRM_MODAL: 'SHOW_DELETE_CONFIRM_MODAL', + END_TRANSITION: 'END_TRANSITION', + OPEN_CALL_POPOVER: 'OPEN_CALL_POPOVER', + CLOSE_CONFIRM_MODAL: 'CLOSE_CONFIRM_MODAL', + MEASURE_CONFIRM_MODAL: 'MEASURE_CONFIRM_MODAL', + SHOW_ATTACHMENTS_POPOVER: 'SHOW_ATTACHMENTS_POPOVER', + CLOSE_ATTACHMENTS_POPOVER: 'CLOSE_ATTACHMENTS_POPOVER', + SHOW_ATTACHMENTS_PICKER_POPOVER: 'SHOW_ATTACHMENTS_PICKER_POPOVER', + CLOSE_EMOJI_PICKER_POPOVER_STANDALONE: 'CLOSE_EMOJI_PICKER_POPOVER_STANDALONE', + MEASURE_CALL_POPOVER: 'MEASURE_CALL_POPOVER', + CLOSE_CALL_POPOVER: 'CLOSE_CALL_POPOVER', +}; + +const States = { + IDLE: 'idle', + KEYBOARD_OPEN: 'keyboardOpen', + POPOVER_OPEN: 'popoverOpen', + POPOVER_CLOSED: 'popoverClosed', + KEYBOARD_POPOVER_CLOSED: 'keyboardPopoverClosed', + KEYBOARD_POPOVER_OPEN: 'keyboardPopoverOpen', + KEYBOARD_CLOSED_POPOVER: 'keyboardClosingPopover', + POPOVER_MEASURED: 'popoverMeasured', + EMOJI_PICKER_POPOVER_OPEN: 'emojiPickerPopoverOpen', + DELETE_MODAL_OPEN: 'deleteModalOpen', + DELETE_MODAL_WITH_KEYBOARD_OPEN: 'deleteModalWithKeyboardOpen', + EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN: 'emojiPickerPopoverWithKeyboardOpen', + EMOJI_PICKER_WITH_KEYBOARD_OPEN: 'emojiPickerWithKeyboardOpen', + CALL_POPOVER_WITH_KEYBOARD_OPEN: 'callPopoverWithKeyboardOpen', + CALL_POPOVER_WITH_KEYBOARD_CLOSED: 'callPopoverWithKeyboardClosed', + ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN: 'attachmentsPopoverWithKeyboardOpen', + ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED: 'attachmentsPopoverWithKeyboardClosed', + MODAL_DELETED: 'modalDeleted', + MODAL_WITH_KEYBOARD_OPEN_DELETED: 'modalWithKeyboardOpenDeleted', +}; + +const STATE_MACHINE = { + [States.IDLE]: { + [Actions.OPEN_POPOVER]: States.POPOVER_OPEN, + [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, + [Actions.MEASURE_POPOVER]: States.IDLE, + [Actions.MEASURE_COMPOSER]: States.IDLE, + [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, + [Actions.SHOW_ATTACHMENTS_PICKER_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, + }, + [States.POPOVER_OPEN]: { + [Actions.CLOSE_POPOVER]: States.POPOVER_CLOSED, + [Actions.MEASURE_POPOVER]: States.POPOVER_OPEN, + [Actions.MEASURE_COMPOSER]: States.POPOVER_OPEN, + [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, + [Actions.POPOVER_ANY_ACTION]: States.POPOVER_CLOSED, + [Actions.HIDE_WITHOUT_ANIMATION]: States.IDLE, + [Actions.EDIT_REPORT]: States.IDLE, + [Actions.SHOW_DELETE_CONFIRM_MODAL]: States.MODAL_DELETED, + }, + [States.POPOVER_CLOSED]: { + [Actions.END_TRANSITION]: States.IDLE, + }, + [States.EMOJI_PICKER_POPOVER_OPEN]: { + [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, + [Actions.CLOSE_EMOJI_PICKER_POPOVER]: States.POPOVER_CLOSED, + }, + [States.MODAL_DELETED]: { + [Actions.MEASURE_CONFIRM_MODAL]: States.MODAL_DELETED, + [Actions.CLOSE_CONFIRM_MODAL]: States.POPOVER_CLOSED, + }, + [States.KEYBOARD_OPEN]: { + [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, + [Actions.OPEN_POPOVER]: States.KEYBOARD_POPOVER_OPEN, + [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.KEYBOARD_POPOVER_OPEN, + [Actions.OPEN_EMOJI_PICKER_POPOVER_STANDALONE]: States.EMOJI_PICKER_WITH_KEYBOARD_OPEN, + [Actions.CLOSE_KEYBOARD]: States.IDLE, + [Actions.OPEN_CALL_POPOVER]: States.CALL_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.SHOW_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.SHOW_ATTACHMENTS_PICKER_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.MEASURE_COMPOSER]: States.KEYBOARD_OPEN, + }, + [States.KEYBOARD_POPOVER_OPEN]: { + [Actions.MEASURE_POPOVER]: States.KEYBOARD_POPOVER_OPEN, + [Actions.MEASURE_COMPOSER]: States.KEYBOARD_POPOVER_OPEN, + [Actions.CLOSE_POPOVER]: States.KEYBOARD_CLOSED_POPOVER, + [Actions.CLOSE_EMOJI_PICKER_POPOVER]: States.KEYBOARD_CLOSED_POPOVER, + [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.KEYBOARD_POPOVER_OPEN, + [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.SHOW_DELETE_CONFIRM_MODAL]: States.MODAL_WITH_KEYBOARD_OPEN_DELETED, + }, + [States.MODAL_WITH_KEYBOARD_OPEN_DELETED]: { + [Actions.MEASURE_CONFIRM_MODAL]: States.MODAL_WITH_KEYBOARD_OPEN_DELETED, + [Actions.CLOSE_CONFIRM_MODAL]: States.KEYBOARD_CLOSED_POPOVER, + }, + [States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN]: { + [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.CLOSE_EMOJI_PICKER_POPOVER]: States.KEYBOARD_CLOSED_POPOVER, + }, + [States.EMOJI_PICKER_WITH_KEYBOARD_OPEN]: { + [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_WITH_KEYBOARD_OPEN, + [Actions.CLOSE_EMOJI_PICKER_POPOVER_STANDALONE]: States.KEYBOARD_POPOVER_CLOSED, + }, + [States.CALL_POPOVER_WITH_KEYBOARD_OPEN]: { + [Actions.MEASURE_POPOVER]: States.CALL_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.MEASURE_CALL_POPOVER]: States.CALL_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.CLOSE_CALL_POPOVER]: States.CALL_POPOVER_WITH_KEYBOARD_CLOSED, + }, + [States.CALL_POPOVER_WITH_KEYBOARD_CLOSED]: { + [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, + }, + [States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN]: { + [Actions.MEASURE_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.MEASURE_COMPOSER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED, + }, + [States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED]: { + [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, + }, + [States.KEYBOARD_POPOVER_CLOSED]: { + [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, + }, + [States.KEYBOARD_CLOSED_POPOVER]: { + [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, + [Actions.END_TRANSITION]: States.KEYBOARD_OPEN, + }, +}; + +function ActionSheetAwareScrollViewProvider(props: PropsWithChildren) { + const {currentState, transition, transitionWorklet, reset} = useWorkletStateMachine(STATE_MACHINE, { + previous: { + state: 'idle', + payload: null, + }, + current: { + state: 'idle', + payload: null, + }, + }); + + const value = useMemo( + () => ({ + currentActionSheetState: currentState, + transitionActionSheetState: transition, + transitionActionSheetStateWorklet: transitionWorklet, + resetStateMachine: reset, + }), + [currentState, reset, transition, transitionWorklet], + ); + + return {props.children}; +} + +ActionSheetAwareScrollViewProvider.propTypes = { + children: PropTypes.node.isRequired, +}; + +export {ActionSheetAwareScrollViewContext, ActionSheetAwareScrollViewProvider, Actions, States}; diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx new file mode 100644 index 000000000000..095cb2556077 --- /dev/null +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -0,0 +1,301 @@ +import React, {useContext, useEffect, useRef} from 'react'; +import type {ViewProps} from 'react-native'; +import {useKeyboardHandler} from 'react-native-keyboard-controller'; +import Reanimated, {interpolate, runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withSequence, withSpring, withTiming} from 'react-native-reanimated'; +import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import {Actions, ActionSheetAwareScrollViewContext, States} from './ActionSheetAwareScrollViewContext'; + +const KeyboardState = { + UNKNOWN: 0, + OPENING: 1, + OPEN: 2, + CLOSING: 3, + CLOSED: 4, +}; +const useAnimatedKeyboard = () => { + const state = useSharedValue(KeyboardState.UNKNOWN); + const height = useSharedValue(0); + const progress = useSharedValue(0); + const heightWhenOpened = useSharedValue(0); + + useKeyboardHandler( + { + onStart: (e) => { + 'worklet'; + + // save the last keyboard height + if (e.height === 0) { + heightWhenOpened.value = height.value; + } + + if (e.height > 0) { + state.value = KeyboardState.OPENING; + } else { + state.value = KeyboardState.CLOSING; + } + }, + onMove: (e) => { + 'worklet'; + + progress.value = e.progress; + height.value = e.height; + }, + onEnd: (e) => { + 'worklet'; + + if (e.height > 0) { + state.value = KeyboardState.OPEN; + } else { + state.value = KeyboardState.CLOSED; + } + + height.value = e.height; + progress.value = e.progress; + }, + }, + [], + ); + + return {state, height, heightWhenOpened, progress}; +}; +const setInitialValueAndRunAnimation = (value: number, animation: number) => { + 'worklet'; + + return withSequence(withTiming(value, {duration: 0}), animation); +}; + +const useSafeAreaPaddings = () => { + const StyleUtils = useStyleUtils(); + const insets = useSafeAreaInsets(); + const {paddingTop, paddingBottom} = StyleUtils.getSafeAreaPadding(insets ?? undefined); + + return {top: paddingTop, bottom: paddingBottom}; +}; + +const config = { + mass: 3, + stiffness: 1000, + damping: 500, +}; + +function ActionSheetKeyboardSpace(props: ViewProps) { + const styles = useThemeStyles(); + const safeArea = useSafeAreaPaddings(); + const keyboard = useAnimatedKeyboard(); + + // similar to using `global` in worklet but it's just a local object + const syncLocalWorkletState = useRef({ + lastState: KeyboardState.UNKNOWN, + }).current; + const {windowHeight} = useWindowDimensions(); + const {currentActionSheetState, transitionActionSheetStateWorklet: transition, transitionActionSheetState, resetStateMachine} = useContext(ActionSheetAwareScrollViewContext); + + // Reset state machine when component unmounts + useEffect(() => () => resetStateMachine(), [resetStateMachine]); + + useAnimatedReaction( + () => keyboard.state.value, + (lastState) => { + if (lastState === syncLocalWorkletState.lastState) { + return; + } + + syncLocalWorkletState.lastState = lastState; + + if (lastState === KeyboardState.OPEN) { + runOnJS(transitionActionSheetState)({ + type: Actions.OPEN_KEYBOARD, + }); + } else if (lastState === KeyboardState.CLOSED) { + runOnJS(transitionActionSheetState)({ + type: Actions.CLOSE_KEYBOARD, + }); + } + }, + [], + ); + + const translateY = useDerivedValue(() => { + const {current, previous} = currentActionSheetState.value; + + // we don't need to run any additional logic + // it will always return 0 for idle state + if (current.state === States.IDLE) { + return withSpring(0, config); + } + + const keyboardHeight = keyboard.height.value === 0 ? 0 : keyboard.height.value - safeArea.bottom; + // sometimes we need to know the last keyboard height + const lastKeyboardHeight = keyboard.heightWhenOpened.value - safeArea.bottom; + + const {popoverHeight = 0, fy, height, composerHeight = 0} = current.payload ?? {}; + + const invertedKeyboardHeight = keyboard.state.value === KeyboardState.CLOSED ? lastKeyboardHeight : 0; + + let elementOffset = 0; + + if (fy !== undefined && height !== undefined && popoverHeight !== undefined) { + elementOffset = fy + safeArea.top + height - (windowHeight - popoverHeight); + } + + // when the sate is not idle we know for sure we have previous state + const previousPayload = previous.payload ?? {}; + + let previousElementOffset = 0; + + if (previousPayload.fy !== undefined && previousPayload.height !== undefined && previousPayload.popoverHeight !== undefined) { + previousElementOffset = previousPayload.fy + safeArea.top + previousPayload.height - (windowHeight - previousPayload.popoverHeight); + } + + // Depending on the current and sometimes previous state we can return + // either animation or just a value + switch (current.state) { + case States.KEYBOARD_OPEN: { + if (previous.state === States.KEYBOARD_CLOSED_POPOVER) { + return Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0) + Math.max(elementOffset, 0); + } + + return withSpring(0, config); + } + + case States.POPOVER_CLOSED: { + return withSpring(0, config, () => { + transition({ + type: Actions.END_TRANSITION, + }); + }); + } + + case States.MODAL_DELETED: + case States.EMOJI_PICKER_POPOVER_OPEN: + case States.POPOVER_OPEN: { + if (popoverHeight) { + if (previousElementOffset !== 0 || elementOffset > previousElementOffset) { + return withSpring(elementOffset < 0 ? 0 : elementOffset, config); + } + + return withSpring(Math.max(previousElementOffset, 0), config); + } + + return 0; + } + + case States.MODAL_WITH_KEYBOARD_OPEN_DELETED: + case States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN: { + // when item is higher than keyboard and bottom sheet + // we should just stay in place + if (elementOffset < 0) { + return invertedKeyboardHeight; + } + + const nextOffset = invertedKeyboardHeight + elementOffset; + if (previous?.payload?.popoverHeight !== popoverHeight) { + const previousOffset = invertedKeyboardHeight + previousElementOffset; + + if (previousElementOffset === 0 || nextOffset > previousOffset) { + return withSpring(nextOffset, config); + } + + return previousOffset; + } + + return nextOffset; + } + + case States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED: + case States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN: { + return interpolate(keyboard.progress.value, [0, 1], [popoverHeight - composerHeight, 0]); + } + case States.CALL_POPOVER_WITH_KEYBOARD_OPEN: { + if (keyboard.height.value > 0) { + return 0; + } + + return setInitialValueAndRunAnimation(lastKeyboardHeight, withSpring(popoverHeight - composerHeight, config)); + } + case States.CALL_POPOVER_WITH_KEYBOARD_CLOSED: { + // keyboard is opened + if (keyboard.height.value > 0) { + return 0; + } + + return withSpring(lastKeyboardHeight, config); + } + case States.EMOJI_PICKER_WITH_KEYBOARD_OPEN: { + if (keyboard.state.value === KeyboardState.CLOSED) { + return popoverHeight - composerHeight; + } + + return 0; + } + + case States.KEYBOARD_POPOVER_CLOSED: { + if (keyboard.heightWhenOpened.value === keyboard.height.value) { + return 0; + } + + return popoverHeight - composerHeight; + } + + case States.KEYBOARD_POPOVER_OPEN: { + if (keyboard.state.value === KeyboardState.OPEN) { + return 0; + } + + const nextOffset = elementOffset + lastKeyboardHeight; + + if (keyboard.state.value === KeyboardState.CLOSED && nextOffset > invertedKeyboardHeight) { + return withSpring(nextOffset < 0 ? 0 : nextOffset, config); + } + + if (elementOffset < 0) { + return lastKeyboardHeight - keyboardHeight; + } + + return lastKeyboardHeight; + } + + case States.KEYBOARD_CLOSED_POPOVER: { + if (elementOffset < 0) { + transition({type: Actions.END_TRANSITION}); + + return 0; + } + + if (keyboard.state.value === KeyboardState.CLOSED) { + return elementOffset + lastKeyboardHeight; + } + + if (keyboard.height.value > 0) { + return keyboard.heightWhenOpened.value - keyboard.height.value + elementOffset; + } + + return withTiming(elementOffset + lastKeyboardHeight, { + duration: 0, + }); + } + + default: + return 0; + } + }, []); + + const animatedStyle = useAnimatedStyle(() => ({ + paddingTop: translateY.value, + })); + + return ( + + ); +} + +ActionSheetKeyboardSpace.displayName = 'ReportKeyboardSpace'; + +export default ActionSheetKeyboardSpace; diff --git a/src/components/ActionSheetAwareScrollView/index.ios.tsx b/src/components/ActionSheetAwareScrollView/index.ios.tsx new file mode 100644 index 000000000000..2c40df7e61c6 --- /dev/null +++ b/src/components/ActionSheetAwareScrollView/index.ios.tsx @@ -0,0 +1,31 @@ +import type {PropsWithChildren} from 'react'; +import React, {forwardRef} from 'react'; +import type {ScrollViewProps} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import {ScrollView} from 'react-native'; +import {Actions, ActionSheetAwareScrollViewContext, ActionSheetAwareScrollViewProvider} from './ActionSheetAwareScrollViewContext'; +import ActionSheetKeyboardSpace from './ActionSheetKeyboardSpace'; + +const ActionSheetAwareScrollView = forwardRef>((props, ref) => ( + + {props.children} + +)); + +export default ActionSheetAwareScrollView; + +/** + * This function should be used as renderScrollComponent prop for FlatList + * @param props - props that will be passed to the ScrollView from FlatList + * @returns - ActionSheetAwareScrollView + */ +function renderScrollComponent(props: ScrollViewProps) { + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +} + +export {renderScrollComponent, ActionSheetAwareScrollViewContext, ActionSheetAwareScrollViewProvider, Actions}; diff --git a/src/components/ActionSheetAwareScrollView/index.tsx b/src/components/ActionSheetAwareScrollView/index.tsx new file mode 100644 index 000000000000..d22f991ce4cf --- /dev/null +++ b/src/components/ActionSheetAwareScrollView/index.tsx @@ -0,0 +1,31 @@ +// this whole file is just for other platforms +// iOS version has everything implemented +import type {PropsWithChildren} from 'react'; +import React, {forwardRef} from 'react'; +import type {ScrollViewProps} from 'react-native'; +// eslint-disable-next-line no-restricted-imports +import {ScrollView} from 'react-native'; +import {Actions, ActionSheetAwareScrollViewContext, ActionSheetAwareScrollViewProvider} from './ActionSheetAwareScrollViewContext'; + +const ActionSheetAwareScrollView = forwardRef>((props, ref) => ( + + {props.children} + +)); + +export default ActionSheetAwareScrollView; + +/** + * This is only used on iOS. On other platforms it's just undefined to be pass a prop to FlatList + * + * This function should be used as renderScrollComponent prop for FlatList + * @param {Object} props - props that will be passed to the ScrollView from FlatList + * @returns {React.ReactElement} - ActionSheetAwareScrollView + */ +const renderScrollComponent = undefined; + +export {renderScrollComponent, ActionSheetAwareScrollViewContext, ActionSheetAwareScrollViewProvider, Actions}; diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index 595e28acd3bc..53f9daf3e044 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -41,7 +41,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow return ( - {({anchor, report, action, checkIfContextMenuActive}) => ( + {({onShowContextMenu, anchor, report, action, checkIfContextMenuActive}) => ( { @@ -53,7 +53,9 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow }} onPressIn={onPressIn} onPressOut={onPressOut} - onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => + onShowContextMenu(() => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))) + } shouldUseHapticsOnLongPress accessibilityLabel={displayName} role={CONST.ROLE.BUTTON} diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index f6730f4b81d9..6824a8f59335 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -110,7 +110,7 @@ const getDataForUpload = (fileData: FileResponse): Promise => { * a callback. This is the ios/android implementation * opening a modal with attachment options */ -function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, shouldHideCameraOption = false}: AttachmentPickerProps) { +function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, shouldHideCameraOption = false, onLayout}: AttachmentPickerProps) { const styles = useThemeStyles(); const [isVisible, setIsVisible] = useState(false); @@ -347,6 +347,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s return ( <> { close(); onCanceled.current(); diff --git a/src/components/AttachmentPicker/types.ts b/src/components/AttachmentPicker/types.ts index 445d79bce07a..8f98275da1bd 100644 --- a/src/components/AttachmentPicker/types.ts +++ b/src/components/AttachmentPicker/types.ts @@ -1,4 +1,5 @@ import type {ReactNode} from 'react'; +import type {LayoutChangeEvent} from 'react-native'; import type {ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; import type CONST from '@src/CONST'; @@ -40,6 +41,12 @@ type AttachmentPickerProps = { /** The types of files that can be selected with this picker. */ type?: ValueOf; + + /** + * Optional callback attached to popover's children container. + * Invoked on Popover mount and layout changes. + */ + onLayout?: ((event: LayoutChangeEvent) => void) | undefined; }; export default AttachmentPickerProps; diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index 26331f92401c..c0fb6d94018d 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -1,6 +1,6 @@ import type {ReactNode} from 'react'; -import React from 'react'; -import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import React, {useCallback, useContext} from 'react'; +import type {LayoutChangeEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -11,6 +11,7 @@ import colors from '@styles/theme/colors'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; +import {Actions, ActionSheetAwareScrollViewContext} from './ActionSheetAwareScrollView'; import Button from './Button'; import Header from './Header'; import Icon from './Icon'; @@ -93,12 +94,27 @@ function ConfirmContent({ iconAdditionalStyles, image, }: ConfirmContentProps) { + const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollViewContext); const styles = useThemeStyles(); const {translate} = useLocalize(); const theme = useTheme(); const {isOffline} = useNetwork(); const StyleUtils = useStyleUtils(); + const onLayout = useCallback( + (event: LayoutChangeEvent) => { + const {height} = event.nativeEvent.layout; + + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: Actions.MEASURE_CONFIRM_MODAL, + payload: { + popoverHeight: height, + }, + }); + }, + [actionSheetAwareScrollViewContext], + ); + const isCentered = shouldCenterContent; return ( @@ -115,7 +131,10 @@ function ConfirmContent({ )} - + {typeof iconSource === 'function' && ( diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 6e0944e5a913..d2e732728af9 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -1,5 +1,6 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {memo, useEffect, useRef} from 'react'; +import React, {memo, useContext, useEffect, useRef} from 'react'; +import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; @@ -30,12 +31,50 @@ type EmojiPickerButtonProps = { }; function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { + const actionSheetContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); const {translate} = useLocalize(); const isFocused = useIsFocused(); + const onPress = () => { + if (!isFocused) { + return; + } + + actionSheetContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.OPEN_EMOJI_PICKER_POPOVER_STANDALONE, + }); + + const onHide = () => { + actionSheetContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.CLOSE_EMOJI_PICKER_POPOVER_STANDALONE, + }); + + if (onModalHide) { + onModalHide(); + } + }; + + if (!EmojiPickerAction.emojiPickerRef.current?.isEmojiPickerVisible) { + EmojiPickerAction.showEmojiPicker( + onHide, + onEmojiSelected, + emojiPopoverAnchor, + { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + shiftVertical, + }, + () => {}, + emojiPickerID, + ); + } else { + EmojiPickerAction.emojiPickerRef.current.hideEmojiPicker(); + } + }; + useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []); return ( @@ -44,27 +83,7 @@ function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shi ref={emojiPopoverAnchor} style={({hovered, pressed}) => [styles.chatItemEmojiButton, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed))]} disabled={isDisabled} - onPress={() => { - if (!isFocused) { - return; - } - if (!EmojiPickerAction.emojiPickerRef?.current?.isEmojiPickerVisible) { - EmojiPickerAction.showEmojiPicker( - onModalHide, - onEmojiSelected, - emojiPopoverAnchor, - { - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, - shiftVertical, - }, - () => {}, - emojiPickerID, - ); - } else { - EmojiPickerAction.emojiPickerRef.current.hideEmojiPicker(); - } - }} + onPress={onPress} id={id} accessibilityLabel={translate('reportActionCompose.emoji')} > diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index b5b4c2d7e71c..01826cd07163 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -1,9 +1,10 @@ import type {ListRenderItem} from '@shopify/flash-list'; import lodashDebounce from 'lodash/debounce'; -import React, {useCallback} from 'react'; +import React, {useCallback, useContext} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import {runOnUI, scrollTo} from 'react-native-reanimated'; +import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; @@ -114,9 +115,25 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r }, [styles, windowWidth, preferredSkinTone, singleExecution, onEmojiSelected, translate, activeEmoji], ); + const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); + const onLayout = useCallback( + (event) => { + const {height} = event.nativeEvent.layout; + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.MEASURE_EMOJI_PICKER_POPOVER, + payload: { + popoverHeight: height, + }, + }); + }, + [actionSheetAwareScrollViewContext], + ); return ( - + - {({anchor, report, action, checkIfContextMenuActive}) => ( + {({onShowContextMenu, anchor, report, action, checkIfContextMenuActive}) => ( {({reportID, accountID, type}) => ( showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => onShowContextMenu(() => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report)))} shouldUseHapticsOnLongPress accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('accessibilityHints.viewAttachment')} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index 504ddecb492b..95c4d7e636d7 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -83,10 +83,12 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona return ( - {({anchor, report, action, checkIfContextMenuActive}) => ( + {({onShowContextMenu, anchor, report, action, checkIfContextMenuActive}) => ( showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => + onShowContextMenu(() => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))) + } onPress={(event) => { event.preventDefault(); Navigation.navigate(navigationRoute); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx index 39a1993c2334..69e9fa0e0f80 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -34,12 +34,14 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d return ( - {({anchor, report, action, checkIfContextMenuActive}) => ( + {({onShowContextMenu, anchor, report, action, checkIfContextMenuActive}) => ( {})} onPressIn={onPressIn} onPressOut={onPressOut} - onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} + onLongPress={(event) => + onShowContextMenu(() => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))) + } shouldUseHapticsOnLongPress role={CONST.ROLE.PRESENTATION} accessibilityLabel={translate('accessibilityHints.prestyledText')} diff --git a/src/components/KeyboardAvoidingView/index.ios.tsx b/src/components/KeyboardAvoidingView/index.ios.tsx index a7cd767377ef..485d230bbfd8 100644 --- a/src/components/KeyboardAvoidingView/index.ios.tsx +++ b/src/components/KeyboardAvoidingView/index.ios.tsx @@ -2,7 +2,7 @@ * The KeyboardAvoidingView is only used on ios */ import React from 'react'; -import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native'; +import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native-keyboard-controller'; import type KeyboardAvoidingViewProps from './types'; function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) { diff --git a/src/components/KeyboardHandlerProvider.tsx b/src/components/KeyboardHandlerProvider.tsx new file mode 100644 index 000000000000..dc208d10aeb3 --- /dev/null +++ b/src/components/KeyboardHandlerProvider.tsx @@ -0,0 +1,12 @@ +import type {PropsWithChildren} from 'react'; +import React from 'react'; +import {Platform} from 'react-native'; +import {KeyboardProvider} from 'react-native-keyboard-controller'; + +type Props = PropsWithChildren; + +function KeyboardHandlerProvider({children}: Props) { + return {children}; +} + +export default KeyboardHandlerProvider; diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index e525d07fa1d3..f0f708d9deb0 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -1,5 +1,6 @@ import type {RefObject} from 'react'; import React, {useEffect, useRef, useState} from 'react'; +import type {LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; import type {ModalProps} from 'react-native-modal'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; @@ -36,6 +37,9 @@ type PopoverMenuProps = Partial & { /** Callback method fired when the user requests to close the modal */ onClose: () => void; + /** Optional callback passed to popover's children container */ + onLayout?: (e: LayoutChangeEvent) => void; + /** State that determines whether to display the modal or not */ isVisible: boolean; @@ -83,6 +87,7 @@ function PopoverMenu({ anchorPosition, anchorRef, onClose, + onLayout, headerText, fromSidebarMediumScreen, anchorAlignment = { @@ -198,7 +203,10 @@ function PopoverMenu({ shouldSetModalVisibility={shouldSetModalVisibility} shouldEnableNewFocusManagement={shouldEnableNewFocusManagement} > - + {!!headerText && {headerText}} {enteredSubMenuIndexes.length > 0 && renderBackButtonItem()} {currentMenuItems.map((item, menuIndex) => ( diff --git a/src/components/PopoverWithMeasuredContent.tsx b/src/components/PopoverWithMeasuredContent.tsx index 32cc589bf0fb..ecfb6c9d3ca7 100644 --- a/src/components/PopoverWithMeasuredContent.tsx +++ b/src/components/PopoverWithMeasuredContent.tsx @@ -1,5 +1,5 @@ import isEqual from 'lodash/isEqual'; -import React, {useMemo, useState} from 'react'; +import React, {useContext, useMemo, useState} from 'react'; import type {LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -8,6 +8,7 @@ import ComposerFocusManager from '@libs/ComposerFocusManager'; import PopoverWithMeasuredContentUtils from '@libs/PopoverWithMeasuredContentUtils'; import CONST from '@src/CONST'; import type {AnchorDimensions, AnchorPosition} from '@src/styles'; +import * as ActionSheetAwareScrollView from './ActionSheetAwareScrollView'; import Popover from './Popover'; import type {PopoverProps} from './Popover/types'; import type {WindowDimensionsProps} from './withWindowDimensions/types'; @@ -61,6 +62,7 @@ function PopoverWithMeasuredContent({ shouldEnableNewFocusManagement, ...props }: PopoverWithMeasuredContentProps) { + const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const styles = useThemeStyles(); const {windowWidth, windowHeight} = useWindowDimensions(); const [popoverWidth, setPopoverWidth] = useState(popoverDimensions.width); @@ -89,9 +91,22 @@ function PopoverWithMeasuredContent({ * Measure the size of the popover's content. */ const measurePopover = ({nativeEvent}: LayoutChangeEvent) => { - setPopoverWidth(nativeEvent.layout.width); - setPopoverHeight(nativeEvent.layout.height); + const {width, height} = nativeEvent.layout; + setPopoverWidth(width); + setPopoverHeight(height); setIsContentMeasured(true); + + // it handles the case when `measurePopover` is called with values like: 192, 192.00003051757812, 192 + // if we update it, then animation in `ActionSheetAwareScrollView` may be re-running + // and we'll see unsynchronized and junky animation + if (actionSheetAwareScrollViewContext.currentActionSheetState.value.current.payload?.popoverHeight !== Math.floor(popoverHeight)) { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.MEASURE_POPOVER, + payload: { + popoverHeight: Math.floor(popoverHeight), + }, + }); + } }; const adjustedAnchorPosition = useMemo(() => { diff --git a/src/components/Reactions/AddReactionBubble.tsx b/src/components/Reactions/AddReactionBubble.tsx index 8364a6658270..e7af464d61f5 100644 --- a/src/components/Reactions/AddReactionBubble.tsx +++ b/src/components/Reactions/AddReactionBubble.tsx @@ -57,9 +57,10 @@ function AddReactionBubble({onSelectEmoji, reportAction, onPressOpenPicker, onWi useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []); const onPress = () => { - const openPicker = (refParam?: PickerRefElement, anchorOrigin?: AnchorOrigin) => { + const openPicker = (refParam?: PickerRefElement, anchorOrigin?: AnchorOrigin, onHide = () => {}) => { EmojiPickerAction.showEmojiPicker( () => { + onHide(); setIsEmojiPickerActive?.(false); }, (emojiCode, emojiObject) => { diff --git a/src/components/Reactions/QuickEmojiReactions/index.native.tsx b/src/components/Reactions/QuickEmojiReactions/index.native.tsx index b0eb88b31b68..6c55beb9741d 100644 --- a/src/components/Reactions/QuickEmojiReactions/index.native.tsx +++ b/src/components/Reactions/QuickEmojiReactions/index.native.tsx @@ -1,10 +1,17 @@ -import React from 'react'; +import React, {useContext} from 'react'; +import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import BaseQuickEmojiReactions from './BaseQuickEmojiReactions'; -import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; +import type {BaseQuickEmojiReactionsProps, OpenPickerCallback, QuickEmojiReactionsProps} from './types'; + +function QuickEmojiReactions({closeContextMenu, onEmojiSelected, ...rest}: QuickEmojiReactionsProps) { + const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); -function QuickEmojiReactions({closeContextMenu, ...rest}: QuickEmojiReactionsProps) { const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.OPEN_EMOJI_PICKER_POPOVER, + }); + // We first need to close the menu as it's a popover. // The picker is a popover as well and on mobile there can only // be one active popover at a time. @@ -13,13 +20,28 @@ function QuickEmojiReactions({closeContextMenu, ...rest}: QuickEmojiReactionsPro // gets closed, before the picker actually opens, we pass the composer // ref as anchor for the emoji picker popover. openPicker?.(ReportActionComposeFocusManager.composerRef); + + openPicker?.(ReportActionComposeFocusManager.composerRef, undefined, () => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.CLOSE_EMOJI_PICKER_POPOVER, + }); + }); }); }; + const onEmojiSelectedCallback: BaseQuickEmojiReactionsProps['onEmojiSelected'] = (emoji, emojiReactions) => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.CLOSE_EMOJI_PICKER_POPOVER, + }); + + onEmojiSelected(emoji, emojiReactions); + }; + return ( ); diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts index 0021f33ce2c0..725b5aea764f 100644 --- a/src/components/Reactions/QuickEmojiReactions/types.ts +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -7,7 +7,7 @@ import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx' type PickerRefElement = RefObject; -type OpenPickerCallback = (element?: PickerRefElement, anchorOrigin?: AnchorOrigin) => void; +type OpenPickerCallback = (element?: PickerRefElement, anchorOrigin?: AnchorOrigin, callback?: () => void) => void; type CloseContextMenuCallback = () => void; diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index c6bf4f9e4016..f932c55b97ce 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -16,6 +16,7 @@ import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx' import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import AddReactionBubble from './AddReactionBubble'; import EmojiReactionBubble from './EmojiReactionBubble'; +import type {OpenPickerCallback} from './QuickEmojiReactions/types'; import ReactionTooltipContent from './ReactionTooltipContent'; type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & { @@ -35,6 +36,15 @@ type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & */ toggleReaction: (emoji: Emoji) => void; + /** + * Function to call when the user presses on the add reaction button. + * This is only called when the user presses on the button, not on the + * reaction bubbles. + * This is optional, because we don't need it everywhere. + * For example in the ReportActionContextMenu we don't need it. + */ + onPressOpenPicker: (openPicker: OpenPickerCallback) => void; + /** We disable reacting with emojis on report actions that have errors */ shouldBlockReactions?: boolean; @@ -79,6 +89,7 @@ function ReportActionItemEmojiReactions({ reportAction, currentUserPersonalDetails, toggleReaction, + onPressOpenPicker, emojiReactions = {}, shouldBlockReactions = false, preferredLocale = CONST.LOCALES.DEFAULT, @@ -170,6 +181,7 @@ function ReportActionItemEmojiReactions({ })} {!shouldBlockReactions && ( void; + /** Callback for measuring child and running a defined callback/action later */ + onShowContextMenu?: (callback: () => void) => void; + /** Whether the IOU is hovered so we can modify its style */ isHovered?: boolean; @@ -69,6 +72,7 @@ function MoneyRequestAction({ reportID, isMostRecentIOUReportAction, contextMenuAnchor, + onShowContextMenu = () => {}, checkIfContextMenuActive = () => {}, chatReport, iouReport, @@ -128,6 +132,7 @@ function MoneyRequestAction({ isTrackExpense={isTrackExpenseAction} action={action} contextMenuAnchor={contextMenuAnchor} + onShowContextMenu={onShowContextMenu} checkIfContextMenuActive={checkIfContextMenuActive} shouldShowPendingConversionMessage={shouldShowPendingConversionMessage} onPreviewPressed={onMoneyRequestPreviewPressed} diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 3331572ab625..ed7df3c4de1e 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -56,6 +56,7 @@ function MoneyRequestPreviewContent({ containerStyles, walletTerms, checkIfContextMenuActive = () => {}, + onShowContextMenu = () => {}, shouldShowPendingConversionMessage = false, isHovered = false, isWhisper = false, @@ -137,7 +138,7 @@ function MoneyRequestPreviewContent({ }; const showContextMenu = (event: GestureResponderEvent) => { - showContextMenuForReport(event, contextMenuAnchor, reportID, action, checkIfContextMenuActive); + onShowContextMenu(() => showContextMenuForReport(event, contextMenuAnchor, reportID, action, checkIfContextMenuActive)); }; const getPreviewHeaderText = (): string => { diff --git a/src/components/ReportActionItem/MoneyRequestPreview/types.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts index 9dcea80fdc05..694f229a8388 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/types.ts +++ b/src/components/ReportActionItem/MoneyRequestPreview/types.ts @@ -51,6 +51,9 @@ type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & { /** Callback for updating context menu active state, used for showing context menu */ checkIfContextMenuActive?: () => void; + /** Callback for measuring child and running a defined callback/action later */ + onShowContextMenu?: (callback: () => void) => void; + /** Extra styles to pass to View wrapper */ containerStyles?: StyleProp; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index be3b104018db..deb8aead451c 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -78,6 +78,9 @@ type ReportPreviewProps = ReportPreviewOnyxProps & { /** Callback for updating context menu active state, used for showing context menu */ checkIfContextMenuActive?: () => void; + /** Callback for measuring child and running a defined callback/action later */ + onShowContextMenu: (callback: () => void) => void; + /** Whether a message is a whisper */ isWhisper?: boolean; @@ -101,6 +104,7 @@ function ReportPreview({ isWhisper = false, checkIfContextMenuActive = () => {}, userWallet, + onShowContextMenu = () => {}, }: ReportPreviewProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -293,7 +297,7 @@ function ReportPreview({ }} onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} - onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)} + onLongPress={(event) => onShowContextMenu(() => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive))} shouldUseHapticsOnLongPress style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} role="button" diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index 8e8b3b930be7..33256116cae7 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -58,9 +58,22 @@ type TaskPreviewProps = WithCurrentUserPersonalDetailsProps & /** Callback for updating context menu active state, used for showing context menu */ checkIfContextMenuActive: () => void; + + /** Callback that will do measure of necessary layout elements and run provided callback */ + onShowContextMenu: (callback: () => void) => void; }; -function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatReportID, checkIfContextMenuActive, currentUserPersonalDetails, isHovered = false}: TaskPreviewProps) { +function TaskPreview({ + taskReport, + taskReportID, + action, + contextMenuAnchor, + chatReportID, + checkIfContextMenuActive, + currentUserPersonalDetails, + onShowContextMenu, + isHovered = false, +}: TaskPreviewProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); @@ -87,7 +100,7 @@ function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatR onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID))} onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} - onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)} + onLongPress={(event) => onShowContextMenu(() => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive))} shouldUseHapticsOnLongPress style={[styles.flexRow, styles.justifyContentBetween]} role={CONST.ROLE.BUTTON} diff --git a/src/components/ShowContextMenuContext.ts b/src/components/ShowContextMenuContext.ts index 3a996a8d2c64..e0ab5a215f17 100644 --- a/src/components/ShowContextMenuContext.ts +++ b/src/components/ShowContextMenuContext.ts @@ -15,10 +15,12 @@ type ShowContextMenuContextProps = { action: OnyxEntry; transactionThreadReport: OnyxEntry; checkIfContextMenuActive: () => void; + onShowContextMenu: (callback: () => void) => void; }; const ShowContextMenuContext = createContext({ anchor: null, + onShowContextMenu: (callback) => callback(), report: null, action: null, transactionThreadReport: null, @@ -58,7 +60,7 @@ function showContextMenuForReport( action?.reportActionID, ReportUtils.getOriginalReportID(reportID, action), undefined, - checkIfContextMenuActive, + undefined, checkIfContextMenuActive, isArchivedRoom, ); diff --git a/src/components/ThreeDotsMenu/index.tsx b/src/components/ThreeDotsMenu/index.tsx index 7c3358e4688c..2e45d9a741df 100644 --- a/src/components/ThreeDotsMenu/index.tsx +++ b/src/components/ThreeDotsMenu/index.tsx @@ -1,7 +1,8 @@ -import React, {useEffect, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import {Actions, ActionSheetAwareScrollViewContext} from '@components/ActionSheetAwareScrollView'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PopoverMenu from '@components/PopoverMenu'; @@ -38,6 +39,7 @@ function ThreeDotsMenu({ disabled = false, modal = {}, }: ThreeDotsMenuProps) { + const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollViewContext); const theme = useTheme(); const styles = useThemeStyles(); const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); @@ -46,19 +48,25 @@ function ThreeDotsMenu({ const isBehindModal = modal?.willAlertModalBecomeVisible && !modal?.isPopover && !shouldOverlay; const showPopoverMenu = () => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: Actions.OPEN_CALL_POPOVER, + }); setPopupMenuVisible(true); }; - const hidePopoverMenu = () => { + const hidePopoverMenu = useCallback(() => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: Actions.CLOSE_CALL_POPOVER, + }); setPopupMenuVisible(false); - }; + }, [actionSheetAwareScrollViewContext]); useEffect(() => { if (!isBehindModal || !isPopupMenuVisible) { return; } hidePopoverMenu(); - }, [isBehindModal, isPopupMenuVisible]); + }, [hidePopoverMenu, isBehindModal, isPopupMenuVisible]); return ( <> diff --git a/src/hooks/useWorkletStateMachine.ts b/src/hooks/useWorkletStateMachine.ts new file mode 100644 index 000000000000..5b09dc22b059 --- /dev/null +++ b/src/hooks/useWorkletStateMachine.ts @@ -0,0 +1,170 @@ +import {useCallback} from 'react'; +import {runOnJS, runOnUI, useSharedValue} from 'react-native-reanimated'; +import Log from '@libs/Log'; + +// When you need to debug state machine change this to true +const DEBUG_MODE = false; + +type Payload = Record; +type ActionWithPayload

= { + type: string; + payload?: P; +}; +type StateHolder

= { + state: string; + payload: P | null; +}; +type State

= { + previous: StateHolder

; + current: StateHolder

; +}; + +type StateMachine = Record>; + +/** + * A hook that creates a state machine that can be used with Reanimated Worklets. + * You can transition state from worklet or from the JS thread. + * + * State machines are helpful for managing complex UI interactions. We want to transition + * between states based on user actions. But also we want to ignore some actions + * when we are in certain states. + * + * For example: + * 1. Initial state is idle. It can react to KEYBOARD_OPEN action. + * 2. We open emoji picker. It sends EMOJI_PICKER_OPEN action. + * 2. There is no handling for this action in idle state so we do nothing. + * 3. We close emoji picker and it sends EMOJI_PICKER_CLOSE action which again does nothing. + * 4. We open keyboard. It sends KEYBOARD_OPEN action. idle can react to this action + * by transitioning into keyboardOpen state + * 5. Our state is keyboardOpen. It can react to KEYBOARD_CLOSE, EMOJI_PICKER_OPEN actions + * 6. We open emoji picker again. It sends EMOJI_PICKER_OPEN action which transitions our state + * into emojiPickerOpen state. Now we react only to EMOJI_PICKER_CLOSE action. + * 7. Before rendering the emoji picker, the app hides the keyboard. + * It sends KEYBOARD_CLOSE action. But we ignore it since our emojiPickerOpen state can only handle + * EMOJI_PICKER_CLOSE action. so we write the logic for handling hiding the keyboard + * but maintaining the offset based on the keyboard state shared value + * 7. we close the picker and send EMOJI_PICKER_CLOSE action which transitions us back into keyboardOpen state. + * + * State machine object example: + * const stateMachine = { + * idle: { + * KEYBOARD_OPEN: 'keyboardOpen', + * }, + * keyboardOpen: { + * KEYBOARD_CLOSE: 'idle', + * EMOJI_PICKER_OPEN: 'emojiPickerOpen', + * }, + * emojiPickerOpen: { + * EMOJI_PICKER_CLOSE: 'keyboardOpen', + * }, + * } + * + * Initial state example: + * { + * previous: null, + * current: { + * state: 'idle', + * payload: null, + * }, + * } + * + * @param stateMachine - a state machine object + * @param initialState - the initial state of the state machine + * @returns an object containing the current state, a transition function, and a reset function + */ +function useWorkletStateMachine

(stateMachine: StateMachine, initialState: State

) { + const currentState = useSharedValue(initialState); + + const log = useCallback((message: string, params?: P | null) => { + 'worklet'; + + if (!DEBUG_MODE) { + return; + } + + // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/restrict-template-expressions + runOnJS(Log.client)(`[StateMachine] ${message}. Params: ${params}`); + }, []); + + const transitionWorklet = useCallback( + (action: ActionWithPayload

) => { + 'worklet'; + + if (!action) { + throw new Error('state machine action is required'); + } + + const state = currentState.value; + + log(`Current STATE: ${state.current.state}`); + log(`Next ACTION: ${action.type}`, action.payload); + + const nextMachine = stateMachine[state.current.state]; + + if (!nextMachine) { + log(`No next machine found for state: ${state.current.state}`); + return; + } + + const nextState = nextMachine[action.type]; + + if (!nextState) { + log(`No next state found for action: ${action.type}`); + return; + } + + let nextPayload; + + if (typeof action.payload === 'undefined') { + // we save previous payload + nextPayload = state.current.payload; + } else { + // we merge previous payload with the new payload + nextPayload = { + ...state.current.payload, + ...action.payload, + }; + } + + log(`Next STATE: ${nextState}`, nextPayload); + + currentState.value = { + previous: state.current, + current: { + state: nextState, + payload: nextPayload, + }, + }; + }, + [currentState, log, stateMachine], + ); + + const resetWorklet = useCallback(() => { + 'worklet'; + + log('RESET STATE MACHINE'); + currentState.value = initialState; + }, [currentState, initialState, log]); + + const reset = useCallback(() => { + runOnUI(resetWorklet)(); + }, [resetWorklet]); + + const transition = useCallback( + (action: ActionWithPayload

) => { + runOnUI(transitionWorklet)(action); + }, + [transitionWorklet], + ); + + return { + currentState, + transitionWorklet, + transition, + reset, + resetWorklet, + }; +} + +export type {ActionWithPayload, State}; +export default useWorkletStateMachine; diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index 46ebdd751762..c6858b57b868 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -1,11 +1,12 @@ import lodashIsEqual from 'lodash/isEqual'; import type {MutableRefObject, RefObject} from 'react'; -import React, {memo, useMemo, useRef, useState} from 'react'; +import React, {memo, useContext, useMemo, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; // eslint-disable-next-line no-restricted-imports import type {GestureResponderEvent, Text as RNText, View as ViewType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import type {ContextMenuItemHandle} from '@components/ContextMenuItem'; import ContextMenuItem from '@components/ContextMenuItem'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; @@ -118,6 +119,7 @@ function BaseReportActionContextMenu({ disabledActions = [], setIsEmojiPickerActive, }: BaseReportActionContextMenuProps) { + const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -241,6 +243,7 @@ function BaseReportActionContextMenu({ draftMessage, selection, close: () => setShouldKeepOpen(false), + transitionActionSheetState: actionSheetAwareScrollViewContext.transitionActionSheetState, openContextMenu: () => setShouldKeepOpen(true), interceptAnonymousUser, openOverflowMenu, diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index efcd08c35a00..95cbf5845ab4 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -7,6 +7,7 @@ import {InteractionManager} from 'react-native'; import type {GestureResponderEvent, Text, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; +import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import * as Expensicons from '@components/Icon/Expensicons'; import MiniQuickEmojiReactions from '@components/Reactions/MiniQuickEmojiReactions'; import QuickEmojiReactions from '@components/Reactions/QuickEmojiReactions'; @@ -30,8 +31,8 @@ import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; import type {Beta, ReportAction, ReportActionReactions, Transaction} from '@src/types/onyx'; import type IconAsset from '@src/types/utils/IconAsset'; +import {clearActiveReportAction, hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; import type {ContextMenuAnchor} from './ReportActionContextMenu'; -import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; /** Gets the HTML version of the message in an action */ function getActionHtml(reportAction: OnyxEntry): string { @@ -73,6 +74,7 @@ type ContextMenuActionPayload = { draftMessage: string; selection: string; close: () => void; + transitionActionSheetState: (params: {type: string; payload?: Record}) => void; openContextMenu: () => void; interceptAnonymousUser: (callback: () => void, isAnonymousAction?: boolean) => void; anchor?: MutableRefObject; @@ -229,7 +231,7 @@ const ContextMenuActions: ContextMenuAction[] = [ icon: Expensicons.Pencil, shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport) => type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && !isArchivedRoom && !isChronosReport, - onPress: (closePopover, {reportID, reportAction, draftMessage}) => { + onPress: (closePopover, {reportID, reportAction, draftMessage, transitionActionSheetState}) => { if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { hideContextMenu(false); const childReportID = reportAction?.childReportID ?? '0'; @@ -247,6 +249,10 @@ const ContextMenuActions: ContextMenuAction[] = [ }; if (closePopover) { + transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.EDIT_REPORT, + }); + // Hide popover, then call editAction hideContextMenu(false, editAction); return; @@ -490,10 +496,21 @@ const ContextMenuActions: ContextMenuAction[] = [ !isArchivedRoom && !isChronosReport && !ReportActionsUtils.isMessageDeleted(reportAction), - onPress: (closePopover, {reportID, reportAction}) => { + onPress: (closePopover, {reportID, reportAction, transitionActionSheetState}) => { if (closePopover) { + transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.SHOW_DELETE_CONFIRM_MODAL, + }); + + const onClose = () => { + transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.CLOSE_CONFIRM_MODAL, + }); + clearActiveReportAction(); + }; + // Hide popover, then call showDeleteConfirmModal - hideContextMenu(false, () => showDeleteModal(reportID, reportAction)); + hideContextMenu(false, () => showDeleteModal(reportID, reportAction, true, onClose, onClose)); return; } diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index 26aefe24bb20..7d2402813193 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -1,8 +1,10 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {useCallback, useEffect, useMemo} from 'react'; +import React, {useCallback, useContext, useEffect, useMemo} from 'react'; +import type {LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; +import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import type {FileObject} from '@components/AttachmentModal'; import AttachmentPicker from '@components/AttachmentPicker'; import Icon from '@components/Icon'; @@ -112,6 +114,7 @@ function AttachmentPickerWithMenuItems({ actionButtonRef, raiseIsScrollLikelyLayoutTriggered, }: AttachmentPickerWithMenuItemsProps) { + const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const isFocused = useIsFocused(); const theme = useTheme(); const styles = useThemeStyles(); @@ -175,6 +178,18 @@ function AttachmentPickerWithMenuItems({ ]; }, [report, reportID, translate]); + const measurePopover = useCallback( + ({nativeEvent}: LayoutChangeEvent) => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.MEASURE_POPOVER, + payload: { + popoverHeight: nativeEvent.layout.height, + }, + }); + }, + [actionSheetAwareScrollViewContext], + ); + const onPopoverMenuClose = () => { setMenuVisibility(false); onMenuClosed(); @@ -296,6 +311,7 @@ function AttachmentPickerWithMenuItems({ { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: isMenuVisible ? ActionSheetAwareScrollView.Actions.SHOW_ATTACHMENTS_POPOVER : ActionSheetAwareScrollView.Actions.CLOSE_ATTACHMENTS_POPOVER, + }); + }, [actionSheetAwareScrollViewContext, isMenuVisible]); + // When we invite someone to a room they don't have the policy object, but we still want them to be able to mention other reports they are members of, so we only check if the policyID in the report is from a workspace const isGroupPolicyReport = useMemo(() => !!report?.policyID && report.policyID !== CONST.POLICY.ID_FAKE, [report]); const reportRecipientAcountIDs = ReportUtils.getReportRecipientAccountIDs(report, currentUserPersonalDetails.accountID); @@ -371,6 +379,18 @@ function ReportActionCompose({ runOnJS(submitForm)(); }, [isSendDisabled, resetFullComposerSize, submitForm, animatedRef, isReportReadyForDisplay]); + const measureComposer = useCallback( + (e: LayoutChangeEvent) => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.MEASURE_COMPOSER, + payload: { + composerHeight: e.nativeEvent.layout.height, + }, + }); + }, + [actionSheetAwareScrollViewContext], + ); + const emojiShiftVertical = useMemo(() => { const chatItemComposeSecondaryRowHeight = styles.chatItemComposeSecondaryRow.height + styles.chatItemComposeSecondaryRow.marginTop + styles.chatItemComposeSecondaryRow.marginBottom; const reportActionComposeHeight = styles.chatItemComposeBox.minHeight + chatItemComposeSecondaryRowHeight; @@ -383,7 +403,10 @@ function ReportActionCompose({ {shouldShowReportRecipientLocalTime && hasReportRecipient && } - + { setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); - }, [action.reportActionID]); + + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.CLOSE_POPOVER, + }); + }, [actionSheetAwareScrollViewContext, action.reportActionID]); + + const handlePressOpenPicker = useCallback( + (openPicker: OpenPickerCallback) => { + if (!(popoverAnchorRef.current && 'measureInWindow' in popoverAnchorRef.current)) { + return; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + popoverAnchorRef.current.measureInWindow((_fx, fy, _width, height) => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.OPEN_EMOJI_PICKER_POPOVER, + payload: { + fy, + height, + }, + }); + + openPicker(undefined, undefined, () => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.CLOSE_EMOJI_PICKER_POPOVER, + }); + }); + }); + }, + [actionSheetAwareScrollViewContext], + ); + + const handleShowContextMenu = useCallback( + (callback: () => void) => { + if (!(popoverAnchorRef.current && 'measureInWindow' in popoverAnchorRef.current)) { + return; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + popoverAnchorRef.current?.measureInWindow((_fx, fy, _width, height) => { + actionSheetAwareScrollViewContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.OPEN_POPOVER, + payload: { + popoverHeight: 0, + fy, + height, + }, + }); + + callback(); + }); + }, + [actionSheetAwareScrollViewContext], + ); /** * Show the ReportActionContextMenu modal popover. @@ -335,29 +391,31 @@ function ReportActionItem({ return; } - setIsContextMenuActive(true); - const selection = SelectionScraper.getCurrentSelection(); - ReportActionContextMenu.showContextMenu( - CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, - event, - selection, - popoverAnchorRef.current, - report.reportID, - action.reportActionID, - originalReportID, - draftMessage ?? '', - () => setIsContextMenuActive(true), - toggleContextMenuFromActiveReportAction, - ReportUtils.isArchivedRoom(originalReport), - ReportUtils.chatIncludesChronos(originalReport), - false, - false, - [], - false, - setIsEmojiPickerActive as () => void, - ); + handleShowContextMenu(() => { + setIsContextMenuActive(true); + const selection = SelectionScraper.getCurrentSelection(); + ReportActionContextMenu.showContextMenu( + CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, + event, + selection, + popoverAnchorRef.current, + report.reportID, + action.reportActionID, + originalReportID, + draftMessage ?? '', + () => setIsContextMenuActive(true), + toggleContextMenuFromActiveReportAction, + ReportUtils.isArchivedRoom(originalReport), + ReportUtils.chatIncludesChronos(originalReport), + false, + false, + [], + false, + setIsEmojiPickerActive as () => void, + ); + }); }, - [draftMessage, action, report.reportID, toggleContextMenuFromActiveReportAction, originalReport, originalReportID], + [draftMessage, action, report.reportID, toggleContextMenuFromActiveReportAction, originalReport, originalReportID, handleShowContextMenu], ); // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. @@ -386,8 +444,9 @@ function ReportActionItem({ action, transactionThreadReport, checkIfContextMenuActive: toggleContextMenuFromActiveReportAction, + onShowContextMenu: handleShowContextMenu, }), - [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport], + [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, handleShowContextMenu], ); const attachmentContextValue = useMemo(() => ({reportID: report.reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [report.reportID]); @@ -530,6 +589,7 @@ function ReportActionItem({ isMostRecentIOUReportAction={isMostRecentIOUReportAction} isHovered={hovered} contextMenuAnchor={popoverAnchorRef.current} + onShowContextMenu={handleShowContextMenu} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} style={displayAsGroup ? [] : [styles.mt2]} isWhisper={isWhisper} @@ -546,6 +606,7 @@ function ReportActionItem({ containerStyles={displayAsGroup ? [] : [styles.mt2]} action={action} isHovered={hovered} + onShowContextMenu={handleShowContextMenu} contextMenuAnchor={popoverAnchorRef.current} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} isWhisper={isWhisper} @@ -561,6 +622,7 @@ function ReportActionItem({ chatReportID={report.reportID} action={action} isHovered={hovered} + onShowContextMenu={handleShowContextMenu} contextMenuAnchor={popoverAnchorRef.current} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} policyID={report.policyID ?? ''} @@ -707,6 +769,7 @@ function ReportActionItem({ {!ReportActionsUtils.isMessageDeleted(action) && ( Date: Fri, 15 Mar 2024 15:50:17 +0100 Subject: [PATCH 002/161] chore: fixed after code review --- .../ActionSheetAwareScrollViewContext.tsx | 2 ++ .../ActionSheetKeyboardSpace.tsx | 17 +++++++---------- .../EmojiPicker/EmojiPickerButton.tsx | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx index a8dda4adb621..59cf826bfa5b 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -12,12 +12,14 @@ type MeasuredElements = { height?: number; composerHeight?: number; }; + type Context = { currentActionSheetState: SharedValue>; transitionActionSheetState: (action: ActionWithPayload) => void; transitionActionSheetStateWorklet: (action: ActionWithPayload) => void; resetStateMachine: () => void; }; + const defaultValue: Context = { currentActionSheetState: { value: { diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index 095cb2556077..75ebf396e155 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -15,6 +15,7 @@ const KeyboardState = { CLOSING: 3, CLOSED: 4, }; + const useAnimatedKeyboard = () => { const state = useSharedValue(KeyboardState.UNKNOWN); const height = useSharedValue(0); @@ -61,6 +62,7 @@ const useAnimatedKeyboard = () => { return {state, height, heightWhenOpened, progress}; }; + const setInitialValueAndRunAnimation = (value: number, animation: number) => { 'worklet'; @@ -135,20 +137,15 @@ function ActionSheetKeyboardSpace(props: ViewProps) { const invertedKeyboardHeight = keyboard.state.value === KeyboardState.CLOSED ? lastKeyboardHeight : 0; - let elementOffset = 0; - - if (fy !== undefined && height !== undefined && popoverHeight !== undefined) { - elementOffset = fy + safeArea.top + height - (windowHeight - popoverHeight); - } + const elementOffset = fy !== undefined && height !== undefined && popoverHeight !== undefined ? fy + safeArea.top + height - (windowHeight - popoverHeight) : 0; // when the sate is not idle we know for sure we have previous state const previousPayload = previous.payload ?? {}; - let previousElementOffset = 0; - - if (previousPayload.fy !== undefined && previousPayload.height !== undefined && previousPayload.popoverHeight !== undefined) { - previousElementOffset = previousPayload.fy + safeArea.top + previousPayload.height - (windowHeight - previousPayload.popoverHeight); - } + const previousElementOffset = + previousPayload.fy !== undefined && previousPayload.height !== undefined && previousPayload.popoverHeight !== undefined + ? previousPayload.fy + safeArea.top + previousPayload.height - (windowHeight - previousPayload.popoverHeight) + : 0; // Depending on the current and sometimes previous state we can return // either animation or just a value diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index d2e732728af9..40e3cdd45f71 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -38,7 +38,7 @@ function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shi const {translate} = useLocalize(); const isFocused = useIsFocused(); - const onPress = () => { + const openEmojiPicker = () => { if (!isFocused) { return; } @@ -83,7 +83,7 @@ function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shi ref={emojiPopoverAnchor} style={({hovered, pressed}) => [styles.chatItemEmojiButton, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed))]} disabled={isDisabled} - onPress={onPress} + onPress={openEmojiPicker} id={id} accessibilityLabel={translate('reportActionCompose.emoji')} > From 30d22dcb7ba4972cf1b0ab3181eaf16c8cfd479d Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 18 Mar 2024 13:32:52 +0100 Subject: [PATCH 003/161] fix: remove resetWorklet since it's not used right now --- src/hooks/useWorkletStateMachine.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hooks/useWorkletStateMachine.ts b/src/hooks/useWorkletStateMachine.ts index 5b09dc22b059..f0c3fde0012d 100644 --- a/src/hooks/useWorkletStateMachine.ts +++ b/src/hooks/useWorkletStateMachine.ts @@ -162,7 +162,6 @@ function useWorkletStateMachine

(stateMachine: StateMachine, initialState: Sta transitionWorklet, transition, reset, - resetWorklet, }; } From a6eebcce3f3b480fd53174453cc3f660ee9352ab Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 19 Mar 2024 16:31:00 +0100 Subject: [PATCH 004/161] fix: frozen KeyboardAvoidingView when it was mounted with an opened keyboard --- .../react-native-keyboard-controller+1.10.4.patch | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 patches/react-native-keyboard-controller+1.10.4.patch diff --git a/patches/react-native-keyboard-controller+1.10.4.patch b/patches/react-native-keyboard-controller+1.10.4.patch new file mode 100644 index 000000000000..ab2d23fcd093 --- /dev/null +++ b/patches/react-native-keyboard-controller+1.10.4.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/react-native-keyboard-controller/src/components/KeyboardAvoidingView/index.tsx b/node_modules/react-native-keyboard-controller/src/components/KeyboardAvoidingView/index.tsx +index 9e73caf..0d5f143 100644 +--- a/node_modules/react-native-keyboard-controller/src/components/KeyboardAvoidingView/index.tsx ++++ b/node_modules/react-native-keyboard-controller/src/components/KeyboardAvoidingView/index.tsx +@@ -81,7 +81,7 @@ const KeyboardAvoidingView = forwardRef>( + const onLayoutWorklet = useCallback((layout: LayoutRectangle) => { + "worklet"; + +- if (keyboard.isClosed.value) { ++ if (keyboard.isClosed.value || initialFrame.value === null) { + initialFrame.value = layout; + } + }, []); From 8b0b29754a7ec780b697954de20b5a2268c835f3 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 21 Mar 2024 12:42:05 +0100 Subject: [PATCH 005/161] chore: update keyboard-controller --- package-lock.json | 8 ++++---- package.json | 2 +- .../react-native-keyboard-controller+1.10.4.patch | 13 ------------- 3 files changed, 5 insertions(+), 18 deletions(-) delete mode 100644 patches/react-native-keyboard-controller+1.10.4.patch diff --git a/package-lock.json b/package-lock.json index d3bcfea84c51..d42a93b9496a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.10.4", + "react-native-keyboard-controller": "^1.11.4", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -31421,9 +31421,9 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.10.4.tgz", - "integrity": "sha512-PQ3AcKGnacDBeA1zB1y44XLgj0sZd3Py5Kpml412bKgYiM09JgoK7YbJcUxMayTeEGtZ8GTOteevGTbGq1Otrg==", + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.11.4.tgz", + "integrity": "sha512-Gn/3d7dut8IyCZikvywKivwrYFfpj3YFaT8YcxtTL46jeoG61qFIjzEV2OPdRy476c6Mea3rByhrdiR30XCfVg==", "peerDependencies": { "react": "*", "react-native": "*", diff --git a/package.json b/package.json index efadc759b85f..72ca02f36d82 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.10.4", + "react-native-keyboard-controller": "^1.11.4", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", diff --git a/patches/react-native-keyboard-controller+1.10.4.patch b/patches/react-native-keyboard-controller+1.10.4.patch deleted file mode 100644 index ab2d23fcd093..000000000000 --- a/patches/react-native-keyboard-controller+1.10.4.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/react-native-keyboard-controller/src/components/KeyboardAvoidingView/index.tsx b/node_modules/react-native-keyboard-controller/src/components/KeyboardAvoidingView/index.tsx -index 9e73caf..0d5f143 100644 ---- a/node_modules/react-native-keyboard-controller/src/components/KeyboardAvoidingView/index.tsx -+++ b/node_modules/react-native-keyboard-controller/src/components/KeyboardAvoidingView/index.tsx -@@ -81,7 +81,7 @@ const KeyboardAvoidingView = forwardRef>( - const onLayoutWorklet = useCallback((layout: LayoutRectangle) => { - "worklet"; - -- if (keyboard.isClosed.value) { -+ if (keyboard.isClosed.value || initialFrame.value === null) { - initialFrame.value = layout; - } - }, []); From 7de98477e75ee0c634ef46fbc8067aa00e1d5ef0 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 21 Mar 2024 13:24:11 +0100 Subject: [PATCH 006/161] fix: composer under keyboard when you press Edit message --- .../ActionSheetAwareScrollViewContext.tsx | 5 +++++ .../ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx index 59cf826bfa5b..f592146659f4 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -90,6 +90,7 @@ const States = { ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED: 'attachmentsPopoverWithKeyboardClosed', MODAL_DELETED: 'modalDeleted', MODAL_WITH_KEYBOARD_OPEN_DELETED: 'modalWithKeyboardOpenDeleted', + EDIT_MESSAGE: 'editMessage', }; const STATE_MACHINE = { @@ -141,6 +142,7 @@ const STATE_MACHINE = { [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.KEYBOARD_POPOVER_OPEN, [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN, [Actions.SHOW_DELETE_CONFIRM_MODAL]: States.MODAL_WITH_KEYBOARD_OPEN_DELETED, + [Actions.EDIT_REPORT]: States.EDIT_MESSAGE, }, [States.MODAL_WITH_KEYBOARD_OPEN_DELETED]: { [Actions.MEASURE_CONFIRM_MODAL]: States.MODAL_WITH_KEYBOARD_OPEN_DELETED, @@ -177,6 +179,9 @@ const STATE_MACHINE = { [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, [Actions.END_TRANSITION]: States.KEYBOARD_OPEN, }, + [States.EDIT_MESSAGE]: { + [Actions.CLOSE_KEYBOARD]: States.IDLE, + }, }; function ActionSheetAwareScrollViewProvider(props: PropsWithChildren) { diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index 75ebf396e155..cfef99c87d08 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -274,6 +274,9 @@ function ActionSheetKeyboardSpace(props: ViewProps) { duration: 0, }); } + case States.EDIT_MESSAGE: { + return 0; + } default: return 0; From d7052062d1cd539a5530d8143c7110c6a9638629 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 27 Mar 2024 13:17:54 +0100 Subject: [PATCH 007/161] chore: use latest RNKC version --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d42a93b9496a..d671b07f21e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.11.4", + "react-native-keyboard-controller": "^1.11.5", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -31421,9 +31421,9 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.11.4.tgz", - "integrity": "sha512-Gn/3d7dut8IyCZikvywKivwrYFfpj3YFaT8YcxtTL46jeoG61qFIjzEV2OPdRy476c6Mea3rByhrdiR30XCfVg==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.11.5.tgz", + "integrity": "sha512-AtlJVz+Sm9uh5Y+k0AhqJDVnbpYkieOE+qHwO8046Fyt9blxK5pGprOQLNy9hcJjfxwhiwiSFk9VyZlZfaYe6Q==", "peerDependencies": { "react": "*", "react-native": "*", diff --git a/package.json b/package.json index 72ca02f36d82..37c2a0af4e35 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.11.4", + "react-native-keyboard-controller": "^1.11.5", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", From f0ed018c225f4fc1222f60849930a1e66a1e0a7a Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 14 May 2024 13:26:32 +0200 Subject: [PATCH 008/161] post rebase changes --- ios/Podfile.lock | 20 +++++++++++++++++++ package-lock.json | 8 ++++---- package.json | 2 +- .../EmojiPickerMenu/index.native.tsx | 3 ++- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1cac95e15036..23558b1a9b7a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1303,6 +1303,25 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-keyboard-controller (1.12.0): + - glog + - hermes-engine + - RCT-Folly (= 2022.05.16.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-launch-arguments (4.0.2): - React - react-native-netinfo (11.2.1): @@ -2544,6 +2563,7 @@ SPEC CHECKSUMS: react-native-geolocation: f9e92eb774cb30ac1e099f34b3a94f03b4db7eb3 react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: 28ccfa09520e7d7e30739480dea4df003493bfe8 + react-native-keyboard-controller: 967a185a802fbc07a8ff4562d246158908bdf590 react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: 02d31de0e08ab043d48f2a1a8baade109d7b6ca5 react-native-pager-view: ccd4bbf9fc7effaf8f91f8dae43389844d9ef9fa diff --git a/package-lock.json b/package-lock.json index d671b07f21e7..434fc0752fca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.11.5", + "react-native-keyboard-controller": "^1.12.0", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -31421,9 +31421,9 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.11.5.tgz", - "integrity": "sha512-AtlJVz+Sm9uh5Y+k0AhqJDVnbpYkieOE+qHwO8046Fyt9blxK5pGprOQLNy9hcJjfxwhiwiSFk9VyZlZfaYe6Q==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.12.0.tgz", + "integrity": "sha512-am/1qIClurdUEgCD43E5/YJEO62XrBJLnvq3dFJt6KJAMN9/f3Ffz04tbQ5xEY5k91uurJX4Ev8bEXXJfaIvaA==", "peerDependencies": { "react": "*", "react-native": "*", diff --git a/package.json b/package.json index 37c2a0af4e35..3dd91101f02f 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.11.5", + "react-native-keyboard-controller": "^1.12.0", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 01826cd07163..41fac94e9e7d 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -2,6 +2,7 @@ import type {ListRenderItem} from '@shopify/flash-list'; import lodashDebounce from 'lodash/debounce'; import React, {useCallback, useContext} from 'react'; import type {ForwardedRef} from 'react'; +import type {LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; import {runOnUI, scrollTo} from 'react-native-reanimated'; import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; @@ -117,7 +118,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r ); const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const onLayout = useCallback( - (event) => { + (event: LayoutChangeEvent) => { const {height} = event.nativeEvent.layout; actionSheetAwareScrollViewContext.transitionActionSheetState({ type: ActionSheetAwareScrollView.Actions.MEASURE_EMOJI_PICKER_POPOVER, From 5fe5b3466675f051d66cd509234b2f4199677dee Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 16 May 2024 11:36:55 +0200 Subject: [PATCH 009/161] feat: update keyboard-controller to avoid crashes --- ios/Podfile.lock | 4 ++-- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 23558b1a9b7a..97397b5e213a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1303,7 +1303,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-keyboard-controller (1.12.0): + - react-native-keyboard-controller (1.12.1): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2563,7 +2563,7 @@ SPEC CHECKSUMS: react-native-geolocation: f9e92eb774cb30ac1e099f34b3a94f03b4db7eb3 react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: 28ccfa09520e7d7e30739480dea4df003493bfe8 - react-native-keyboard-controller: 967a185a802fbc07a8ff4562d246158908bdf590 + react-native-keyboard-controller: 36bc3176750b519d746aa5f328fdac7ec2e82bb2 react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: 02d31de0e08ab043d48f2a1a8baade109d7b6ca5 react-native-pager-view: ccd4bbf9fc7effaf8f91f8dae43389844d9ef9fa diff --git a/package-lock.json b/package-lock.json index 434fc0752fca..562a433c14f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.12.0", + "react-native-keyboard-controller": "^1.12.1", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -31421,9 +31421,9 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.12.0.tgz", - "integrity": "sha512-am/1qIClurdUEgCD43E5/YJEO62XrBJLnvq3dFJt6KJAMN9/f3Ffz04tbQ5xEY5k91uurJX4Ev8bEXXJfaIvaA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.12.1.tgz", + "integrity": "sha512-2OpQcesiYsMilrTzgcTafSGexd9UryRQRuHudIcOn0YaqvvzNpnhVZMVuJMH93fJv/iaZYp3138rgUKOdHhtSw==", "peerDependencies": { "react": "*", "react-native": "*", diff --git a/package.json b/package.json index 3dd91101f02f..78c2f5ec4415 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.12.0", + "react-native-keyboard-controller": "^1.12.1", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", From acfbdcb0f8bb35655522bbcc858e7411cbf7063b Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 4 Jun 2024 17:25:03 +0200 Subject: [PATCH 010/161] chore: apply prettier --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx | 4 +++- src/pages/home/report/ReportActionItem.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 902489731d94..d8b13f08909b 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -93,7 +93,9 @@ function ImageRenderer({tnode}: ImageRendererProps) { Navigation.navigate(route); } }} - onLongPress={(event) => onShowContextMenu(() => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report)))} + onLongPress={(event) => + onShowContextMenu(() => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))) + } shouldUseHapticsOnLongPress accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('accessibilityHints.viewAttachment')} diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 58d0e606c8a9..196dd2a825fb 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -5,8 +5,8 @@ import {InteractionManager, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; -import {AttachmentContext} from '@components/AttachmentContext'; import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; +import {AttachmentContext} from '@components/AttachmentContext'; import Button from '@components/Button'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; From 41044a199b8734617e0d8fe5af9a10ce02cd1fe9 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 20 Jun 2024 15:04:42 +0300 Subject: [PATCH 011/161] chore: keep important changes from experiments --- ...ated+3.8.1+003+concurrent-rn-updates.patch | 79 +++++++++++++++++++ src/hooks/useWorkletStateMachine.ts | 5 +- 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 patches/react-native-reanimated+3.8.1+003+concurrent-rn-updates.patch diff --git a/patches/react-native-reanimated+3.8.1+003+concurrent-rn-updates.patch b/patches/react-native-reanimated+3.8.1+003+concurrent-rn-updates.patch new file mode 100644 index 000000000000..02a298f4c5a6 --- /dev/null +++ b/patches/react-native-reanimated+3.8.1+003+concurrent-rn-updates.patch @@ -0,0 +1,79 @@ +diff --git a/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.cpp +index b34579d..87513aa 100644 +--- a/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.cpp ++++ b/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.cpp +@@ -418,6 +418,24 @@ bool NativeReanimatedModule::isThereAnyLayoutProp( + return false; + } + ++jsi::Object NativeReanimatedModule::getUIProps( ++ jsi::Runtime &rt, ++ const jsi::Object &props) { ++ jsi::Object res = jsi::Object(rt); ++ const jsi::Array propNames = props.getPropertyNames(rt); ++ for (size_t i = 0; i < propNames.size(rt); ++i) { ++ const std::string propName = ++ propNames.getValueAtIndex(rt, i).asString(rt).utf8(rt); ++ bool isLayoutProp = ++ nativePropNames_.find(propName) != nativePropNames_.end(); ++ if (!isLayoutProp) { ++ const jsi::Value &propValue = props.getProperty(rt, propName.c_str()); ++ res.setProperty(rt, propName.c_str(), propValue); ++ } ++ } ++ return res; ++} ++ + jsi::Value NativeReanimatedModule::filterNonAnimatableProps( + jsi::Runtime &rt, + const jsi::Value &props) { +@@ -565,13 +583,15 @@ void NativeReanimatedModule::performOperations() { + } + } + ++ // If there's no layout props to be updated, we can apply the updates ++ // directly onto the components and skip the commit. ++ for (const auto &[shadowNode, props] : copiedOperationsQueue) { ++ Tag tag = shadowNode->getTag(); ++ jsi::Object uiProps = getUIProps(rt, props->asObject(rt)); ++ synchronouslyUpdateUIPropsFunction_(rt, tag, uiProps); ++ } ++ + if (!hasLayoutUpdates) { +- // If there's no layout props to be updated, we can apply the updates +- // directly onto the components and skip the commit. +- for (const auto &[shadowNode, props] : copiedOperationsQueue) { +- Tag tag = shadowNode->getTag(); +- synchronouslyUpdateUIPropsFunction_(rt, tag, props->asObject(rt)); +- } + return; + } + +diff --git a/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.h b/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.h +index 9f8c32d..cb31205 100644 +--- a/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.h ++++ b/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.h +@@ -163,6 +163,7 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec { + + #ifdef RCT_NEW_ARCH_ENABLED + bool isThereAnyLayoutProp(jsi::Runtime &rt, const jsi::Object &props); ++ jsi::Object getUIProps(jsi::Runtime &rt, const jsi::Object &props); + jsi::Value filterNonAnimatableProps( + jsi::Runtime &rt, + const jsi::Value &props); +diff --git a/node_modules/react-native-reanimated/apple/REANodesManager.mm b/node_modules/react-native-reanimated/apple/REANodesManager.mm +index ed36c99..0c64925 100644 +--- a/node_modules/react-native-reanimated/apple/REANodesManager.mm ++++ b/node_modules/react-native-reanimated/apple/REANodesManager.mm +@@ -432,9 +432,9 @@ - (void)synchronouslyUpdateViewOnUIThread:(nonnull NSNumber *)viewTag props:(non + REAUIView *componentView = + [componentViewRegistry findComponentViewWithTag:[viewTag integerValue]]; + +- NSSet *propKeysManagedByAnimated = [componentView propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN]; ++ // NSSet *propKeysManagedByAnimated = [componentView propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN]; + [surfacePresenter synchronouslyUpdateViewOnUIThread:viewTag props:uiProps]; +- [componentView setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:propKeysManagedByAnimated]; ++ // [componentView setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:propKeysManagedByAnimated]; + + // `synchronouslyUpdateViewOnUIThread` does not flush props like `backgroundColor` etc. + // so that's why we need to call `finalizeUpdates` here. diff --git a/src/hooks/useWorkletStateMachine.ts b/src/hooks/useWorkletStateMachine.ts index f0c3fde0012d..5bc81a0629b1 100644 --- a/src/hooks/useWorkletStateMachine.ts +++ b/src/hooks/useWorkletStateMachine.ts @@ -21,6 +21,9 @@ type State

= { type StateMachine = Record>; +// eslint-disable-next-line @typescript-eslint/unbound-method +const client = Log.client; + /** * A hook that creates a state machine that can be used with Reanimated Worklets. * You can transition state from worklet or from the JS thread. @@ -83,7 +86,7 @@ function useWorkletStateMachine

(stateMachine: StateMachine, initialState: Sta } // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/restrict-template-expressions - runOnJS(Log.client)(`[StateMachine] ${message}. Params: ${params}`); + runOnJS(client)(`[StateMachine] ${message}. Params: ${JSON.stringify(params)}`); }, []); const transitionWorklet = useCallback( From 843bf2580a2c1102b27fab83f0711909a9c90d55 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 20 Jun 2024 15:56:10 +0300 Subject: [PATCH 012/161] fix: don't mount two providers --- src/App.tsx | 2 -- src/components/KeyboardHandlerProvider.tsx | 12 ------------ 2 files changed, 14 deletions(-) delete mode 100644 src/components/KeyboardHandlerProvider.tsx diff --git a/src/App.tsx b/src/App.tsx index 4adadbce7cc9..56c3b167ddac 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,6 @@ import CustomStatusBarAndBackgroundContextProvider from './components/CustomStat import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import InitialURLContextProvider from './components/InitialURLContextProvider'; -import KeyboardHandlerProvider from './components/KeyboardHandlerProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; @@ -82,7 +81,6 @@ function App({url}: AppProps) { CustomStatusBarAndBackgroundContextProvider, ActiveElementRoleProvider, ActiveWorkspaceContextProvider, - KeyboardHandlerProvider, ActionSheetAwareScrollView.ActionSheetAwareScrollViewProvider, ReportIDsContextProvider, PlaybackContextProvider, diff --git a/src/components/KeyboardHandlerProvider.tsx b/src/components/KeyboardHandlerProvider.tsx deleted file mode 100644 index dc208d10aeb3..000000000000 --- a/src/components/KeyboardHandlerProvider.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type {PropsWithChildren} from 'react'; -import React from 'react'; -import {Platform} from 'react-native'; -import {KeyboardProvider} from 'react-native-keyboard-controller'; - -type Props = PropsWithChildren; - -function KeyboardHandlerProvider({children}: Props) { - return {children}; -} - -export default KeyboardHandlerProvider; From 7d2df0a0b8a89bf5371487edc22beb62f2e23b3b Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 20 Jun 2024 18:05:17 +0300 Subject: [PATCH 013/161] fix: + button transitions --- .../ActionSheetKeyboardSpace.tsx | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index cfef99c87d08..a7154ac9d19d 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -155,10 +155,12 @@ function ActionSheetKeyboardSpace(props: ViewProps) { return Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0) + Math.max(elementOffset, 0); } + console.log(111, 0); return withSpring(0, config); } case States.POPOVER_CLOSED: { + console.log(112, 0); return withSpring(0, config, () => { transition({ type: Actions.END_TRANSITION, @@ -171,12 +173,15 @@ function ActionSheetKeyboardSpace(props: ViewProps) { case States.POPOVER_OPEN: { if (popoverHeight) { if (previousElementOffset !== 0 || elementOffset > previousElementOffset) { + console.log(113, elementOffset < 0 ? 0 : elementOffset, elementOffset); return withSpring(elementOffset < 0 ? 0 : elementOffset, config); } + console.log(114, Math.max(previousElementOffset, 0), previousElementOffset); return withSpring(Math.max(previousElementOffset, 0), config); } + console.log(115, 0); return 0; } @@ -185,6 +190,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { // when item is higher than keyboard and bottom sheet // we should just stay in place if (elementOffset < 0) { + console.log(116, invertedKeyboardHeight); return invertedKeyboardHeight; } @@ -193,65 +199,93 @@ function ActionSheetKeyboardSpace(props: ViewProps) { const previousOffset = invertedKeyboardHeight + previousElementOffset; if (previousElementOffset === 0 || nextOffset > previousOffset) { + console.log(117, nextOffset); return withSpring(nextOffset, config); } + console.log(118, previousOffset); return previousOffset; } - + console.log(119, nextOffset); return nextOffset; } case States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED: case States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN: { + // this transition is extremely slow and we may not have `popoverHeight` when keyboard is hiding + // so we run two fold animation: + // - when keyboard is hiding -> we return `0` and thus the content is sticky to composer + // - when keyboard is closed and we have `popoverHeight` (i. e. popup was measured) -> we run spring animation + if (keyboard.state.value === KeyboardState.CLOSING) { + console.log(1200, 0); + return 0; + } + if (keyboard.progress.value === 0) { + console.log(1201, keyboard.progress.value, interpolate(keyboard.progress.value, [0, 1], [popoverHeight - composerHeight, 0]), popoverHeight, composerHeight); + return withSpring(popoverHeight - composerHeight, config); + } + + // when keyboard appears -> we already have all values so we do interpolation based on keyboard position + console.log(1202, keyboard.progress.value, interpolate(keyboard.progress.value, [0, 1], [popoverHeight - composerHeight, 0]), popoverHeight, composerHeight); return interpolate(keyboard.progress.value, [0, 1], [popoverHeight - composerHeight, 0]); } case States.CALL_POPOVER_WITH_KEYBOARD_OPEN: { if (keyboard.height.value > 0) { + console.log(121, 0); return 0; } - + console.log(122, lastKeyboardHeight, popoverHeight - composerHeight); return setInitialValueAndRunAnimation(lastKeyboardHeight, withSpring(popoverHeight - composerHeight, config)); } case States.CALL_POPOVER_WITH_KEYBOARD_CLOSED: { // keyboard is opened if (keyboard.height.value > 0) { + console.log(123, 0); return 0; } + console.log(124, lastKeyboardHeight); return withSpring(lastKeyboardHeight, config); } case States.EMOJI_PICKER_WITH_KEYBOARD_OPEN: { if (keyboard.state.value === KeyboardState.CLOSED) { + console.log(125, popoverHeight - composerHeight); return popoverHeight - composerHeight; } + console.log(126, 0); return 0; } case States.KEYBOARD_POPOVER_CLOSED: { if (keyboard.heightWhenOpened.value === keyboard.height.value) { + console.log(127, 0); return 0; } + console.log(128, popoverHeight - composerHeight); return popoverHeight - composerHeight; } case States.KEYBOARD_POPOVER_OPEN: { if (keyboard.state.value === KeyboardState.OPEN) { + console.log(129, 0); return 0; } const nextOffset = elementOffset + lastKeyboardHeight; if (keyboard.state.value === KeyboardState.CLOSED && nextOffset > invertedKeyboardHeight) { + console.log(130, nextOffset < 0 ? 0 : nextOffset, nextOffset); return withSpring(nextOffset < 0 ? 0 : nextOffset, config); } if (elementOffset < 0) { + console.log(131, lastKeyboardHeight - keyboardHeight); return lastKeyboardHeight - keyboardHeight; } + console.log(132, lastKeyboardHeight); return lastKeyboardHeight; } @@ -259,26 +293,32 @@ function ActionSheetKeyboardSpace(props: ViewProps) { if (elementOffset < 0) { transition({type: Actions.END_TRANSITION}); + console.log(133, 0); return 0; } if (keyboard.state.value === KeyboardState.CLOSED) { + console.log(134, elementOffset + lastKeyboardHeight); return elementOffset + lastKeyboardHeight; } if (keyboard.height.value > 0) { + console.log(135, keyboard.heightWhenOpened.value - keyboard.height.value + elementOffset); return keyboard.heightWhenOpened.value - keyboard.height.value + elementOffset; } + console.log(136, elementOffset + lastKeyboardHeight); return withTiming(elementOffset + lastKeyboardHeight, { duration: 0, }); } case States.EDIT_MESSAGE: { + console.log(137, 0); return 0; } default: + console.log(138, 0); return 0; } }, []); From 5d3c6e7aa0d4cfbc1fda0e53b273597a4df4a786 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 19 Jul 2024 17:52:11 +0200 Subject: [PATCH 014/161] use executeOnUIRuntimeSync --- src/hooks/useWorkletStateMachine.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useWorkletStateMachine.ts b/src/hooks/useWorkletStateMachine.ts index 5bc81a0629b1..d54a586b1a42 100644 --- a/src/hooks/useWorkletStateMachine.ts +++ b/src/hooks/useWorkletStateMachine.ts @@ -1,5 +1,5 @@ import {useCallback} from 'react'; -import {runOnJS, runOnUI, useSharedValue} from 'react-native-reanimated'; +import {executeOnUIRuntimeSync, runOnJS, useSharedValue} from 'react-native-reanimated'; import Log from '@libs/Log'; // When you need to debug state machine change this to true @@ -150,12 +150,12 @@ function useWorkletStateMachine

(stateMachine: StateMachine, initialState: Sta }, [currentState, initialState, log]); const reset = useCallback(() => { - runOnUI(resetWorklet)(); + executeOnUIRuntimeSync(resetWorklet)(); }, [resetWorklet]); const transition = useCallback( (action: ActionWithPayload

) => { - runOnUI(transitionWorklet)(action); + executeOnUIRuntimeSync(transitionWorklet)(action); }, [transitionWorklet], ); From 67d5dcab3aa251ae6d2062170335df4a22ecfa95 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 25 Jul 2024 16:03:59 +0200 Subject: [PATCH 015/161] reset worklet on UI thread --- src/hooks/useWorkletStateMachine.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useWorkletStateMachine.ts b/src/hooks/useWorkletStateMachine.ts index d54a586b1a42..b9c02faa7bd1 100644 --- a/src/hooks/useWorkletStateMachine.ts +++ b/src/hooks/useWorkletStateMachine.ts @@ -1,5 +1,5 @@ import {useCallback} from 'react'; -import {executeOnUIRuntimeSync, runOnJS, useSharedValue} from 'react-native-reanimated'; +import {executeOnUIRuntimeSync, runOnJS, runOnUI, useSharedValue} from 'react-native-reanimated'; import Log from '@libs/Log'; // When you need to debug state machine change this to true @@ -150,7 +150,7 @@ function useWorkletStateMachine

(stateMachine: StateMachine, initialState: Sta }, [currentState, initialState, log]); const reset = useCallback(() => { - executeOnUIRuntimeSync(resetWorklet)(); + runOnUI(resetWorklet)(); }, [resetWorklet]); const transition = useCallback( From cbeec879a57b73893134b37a47069c0cec2aa37c Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 26 Jul 2024 12:56:12 +0200 Subject: [PATCH 016/161] fix jumping while KEYBOARD_OPEN --- .../ActionSheetKeyboardSpace.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index a7154ac9d19d..afd6bcf7fdea 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -97,10 +97,9 @@ function ActionSheetKeyboardSpace(props: ViewProps) { // Reset state machine when component unmounts useEffect(() => () => resetStateMachine(), [resetStateMachine]); - useAnimatedReaction( () => keyboard.state.value, - (lastState) => { + (lastState, prev) => { if (lastState === syncLocalWorkletState.lastState) { return; } @@ -149,9 +148,10 @@ function ActionSheetKeyboardSpace(props: ViewProps) { // Depending on the current and sometimes previous state we can return // either animation or just a value + switch (current.state) { case States.KEYBOARD_OPEN: { - if (previous.state === States.KEYBOARD_CLOSED_POPOVER) { + if (previous.state === States.KEYBOARD_CLOSED_POPOVER || (previous.state === States.KEYBOARD_OPEN && elementOffset < 0)) { return Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0) + Math.max(elementOffset, 0); } @@ -285,7 +285,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { return lastKeyboardHeight - keyboardHeight; } - console.log(132, lastKeyboardHeight); + console.log(132, lastKeyboardHeight, lastKeyboardHeight - keyboardHeight, keyboardHeight); return lastKeyboardHeight; } @@ -303,7 +303,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { } if (keyboard.height.value > 0) { - console.log(135, keyboard.heightWhenOpened.value - keyboard.height.value + elementOffset); + console.log(135, keyboard.heightWhenOpened.value - keyboard.height.value + elementOffset, 'elementOffset', elementOffset); return keyboard.heightWhenOpened.value - keyboard.height.value + elementOffset; } From 812e9ca431df6983111ec8c8846327bce2d07fd1 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 26 Jul 2024 12:56:43 +0200 Subject: [PATCH 017/161] update controller --- ...ct-native-keyboard-controller+1.12.2.patch | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 patches/react-native-keyboard-controller+1.12.2.patch diff --git a/patches/react-native-keyboard-controller+1.12.2.patch b/patches/react-native-keyboard-controller+1.12.2.patch deleted file mode 100644 index 3c8034354481..000000000000 --- a/patches/react-native-keyboard-controller+1.12.2.patch +++ /dev/null @@ -1,39 +0,0 @@ -diff --git a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -index 83884d8..5d9e989 100644 ---- a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -+++ b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -@@ -99,12 +99,12 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R - } - - private fun goToEdgeToEdge(edgeToEdge: Boolean) { -- reactContext.currentActivity?.let { -- WindowCompat.setDecorFitsSystemWindows( -- it.window, -- !edgeToEdge, -- ) -- } -+ // reactContext.currentActivity?.let { -+ // WindowCompat.setDecorFitsSystemWindows( -+ // it.window, -+ // !edgeToEdge, -+ // ) -+ // } - } - - private fun setupKeyboardCallbacks() { -@@ -158,13 +158,13 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R - // region State managers - private fun enable() { - this.goToEdgeToEdge(true) -- this.setupWindowInsets() -+ // this.setupWindowInsets() - this.setupKeyboardCallbacks() - } - - private fun disable() { - this.goToEdgeToEdge(false) -- this.setupWindowInsets() -+ // this.setupWindowInsets() - this.removeKeyboardCallbacks() - } - // endregion \ No newline at end of file From 05677c9408bdd663e0a1c7ffb2446c6e393e50af Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 26 Jul 2024 17:20:31 +0200 Subject: [PATCH 018/161] update SafeAreaPaddings --- .../ActionSheetKeyboardSpace.tsx | 69 +++++++------------ 1 file changed, 26 insertions(+), 43 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index afd6bcf7fdea..c2214dea813b 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -16,6 +16,12 @@ const KeyboardState = { CLOSED: 4, }; +const SPRING_CONFIG = { + mass: 3, + stiffness: 1000, + damping: 500, +}; + const useAnimatedKeyboard = () => { const state = useSharedValue(KeyboardState.UNKNOWN); const height = useSharedValue(0); @@ -31,12 +37,7 @@ const useAnimatedKeyboard = () => { if (e.height === 0) { heightWhenOpened.value = height.value; } - - if (e.height > 0) { - state.value = KeyboardState.OPENING; - } else { - state.value = KeyboardState.CLOSING; - } + state.value = e.height > 0 ? KeyboardState.OPENING : KeyboardState.CLOSING; }, onMove: (e) => { 'worklet'; @@ -46,13 +47,7 @@ const useAnimatedKeyboard = () => { }, onEnd: (e) => { 'worklet'; - - if (e.height > 0) { - state.value = KeyboardState.OPEN; - } else { - state.value = KeyboardState.CLOSED; - } - + state.value = e.height > 0 ? KeyboardState.OPEN : KeyboardState.CLOSED; height.value = e.height; progress.value = e.progress; }, @@ -77,12 +72,6 @@ const useSafeAreaPaddings = () => { return {top: paddingTop, bottom: paddingBottom}; }; -const config = { - mass: 3, - stiffness: 1000, - damping: 500, -}; - function ActionSheetKeyboardSpace(props: ViewProps) { const styles = useThemeStyles(); const safeArea = useSafeAreaPaddings(); @@ -97,23 +86,22 @@ function ActionSheetKeyboardSpace(props: ViewProps) { // Reset state machine when component unmounts useEffect(() => () => resetStateMachine(), [resetStateMachine]); + // eslint-disable-next-line arrow-body-style + // useEffect(() => { + // return () => resetStateMachine(); + // }, [resetStateMachine]); + useAnimatedReaction( () => keyboard.state.value, - (lastState, prev) => { + (lastState) => { if (lastState === syncLocalWorkletState.lastState) { return; } - syncLocalWorkletState.lastState = lastState; - if (lastState === KeyboardState.OPEN) { - runOnJS(transitionActionSheetState)({ - type: Actions.OPEN_KEYBOARD, - }); + runOnJS(transitionActionSheetState)({type: Actions.OPEN_KEYBOARD}); } else if (lastState === KeyboardState.CLOSED) { - runOnJS(transitionActionSheetState)({ - type: Actions.CLOSE_KEYBOARD, - }); + runOnJS(transitionActionSheetState)({type: Actions.CLOSE_KEYBOARD}); } }, [], @@ -125,22 +113,17 @@ function ActionSheetKeyboardSpace(props: ViewProps) { // we don't need to run any additional logic // it will always return 0 for idle state if (current.state === States.IDLE) { - return withSpring(0, config); + return withSpring(0, SPRING_CONFIG); } const keyboardHeight = keyboard.height.value === 0 ? 0 : keyboard.height.value - safeArea.bottom; // sometimes we need to know the last keyboard height const lastKeyboardHeight = keyboard.heightWhenOpened.value - safeArea.bottom; - const {popoverHeight = 0, fy, height, composerHeight = 0} = current.payload ?? {}; - const invertedKeyboardHeight = keyboard.state.value === KeyboardState.CLOSED ? lastKeyboardHeight : 0; - const elementOffset = fy !== undefined && height !== undefined && popoverHeight !== undefined ? fy + safeArea.top + height - (windowHeight - popoverHeight) : 0; - // when the sate is not idle we know for sure we have previous state const previousPayload = previous.payload ?? {}; - const previousElementOffset = previousPayload.fy !== undefined && previousPayload.height !== undefined && previousPayload.popoverHeight !== undefined ? previousPayload.fy + safeArea.top + previousPayload.height - (windowHeight - previousPayload.popoverHeight) @@ -156,12 +139,12 @@ function ActionSheetKeyboardSpace(props: ViewProps) { } console.log(111, 0); - return withSpring(0, config); + return withSpring(0, SPRING_CONFIG); } case States.POPOVER_CLOSED: { console.log(112, 0); - return withSpring(0, config, () => { + return withSpring(0, SPRING_CONFIG, () => { transition({ type: Actions.END_TRANSITION, }); @@ -174,11 +157,11 @@ function ActionSheetKeyboardSpace(props: ViewProps) { if (popoverHeight) { if (previousElementOffset !== 0 || elementOffset > previousElementOffset) { console.log(113, elementOffset < 0 ? 0 : elementOffset, elementOffset); - return withSpring(elementOffset < 0 ? 0 : elementOffset, config); + return withSpring(elementOffset < 0 ? 0 : elementOffset, SPRING_CONFIG); } console.log(114, Math.max(previousElementOffset, 0), previousElementOffset); - return withSpring(Math.max(previousElementOffset, 0), config); + return withSpring(Math.max(previousElementOffset, 0), SPRING_CONFIG); } console.log(115, 0); @@ -200,7 +183,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { if (previousElementOffset === 0 || nextOffset > previousOffset) { console.log(117, nextOffset); - return withSpring(nextOffset, config); + return withSpring(nextOffset, SPRING_CONFIG); } console.log(118, previousOffset); @@ -222,7 +205,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { } if (keyboard.progress.value === 0) { console.log(1201, keyboard.progress.value, interpolate(keyboard.progress.value, [0, 1], [popoverHeight - composerHeight, 0]), popoverHeight, composerHeight); - return withSpring(popoverHeight - composerHeight, config); + return withSpring(popoverHeight - composerHeight, SPRING_CONFIG); } // when keyboard appears -> we already have all values so we do interpolation based on keyboard position @@ -235,7 +218,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { return 0; } console.log(122, lastKeyboardHeight, popoverHeight - composerHeight); - return setInitialValueAndRunAnimation(lastKeyboardHeight, withSpring(popoverHeight - composerHeight, config)); + return setInitialValueAndRunAnimation(lastKeyboardHeight, withSpring(popoverHeight - composerHeight, SPRING_CONFIG)); } case States.CALL_POPOVER_WITH_KEYBOARD_CLOSED: { // keyboard is opened @@ -245,7 +228,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { } console.log(124, lastKeyboardHeight); - return withSpring(lastKeyboardHeight, config); + return withSpring(lastKeyboardHeight, SPRING_CONFIG); } case States.EMOJI_PICKER_WITH_KEYBOARD_OPEN: { if (keyboard.state.value === KeyboardState.CLOSED) { @@ -277,7 +260,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { if (keyboard.state.value === KeyboardState.CLOSED && nextOffset > invertedKeyboardHeight) { console.log(130, nextOffset < 0 ? 0 : nextOffset, nextOffset); - return withSpring(nextOffset < 0 ? 0 : nextOffset, config); + return withSpring(nextOffset < 0 ? 0 : nextOffset, SPRING_CONFIG); } if (elementOffset < 0) { From 31a74624418532d667e4eb7d35b55cd33f1fed40 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 26 Jul 2024 17:20:57 +0200 Subject: [PATCH 019/161] bump keyboard-controller --- ios/Podfile.lock | 4 ++-- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e7854c886c75..5729dbbd2906 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1303,7 +1303,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-keyboard-controller (1.12.2): + - react-native-keyboard-controller (1.12.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2585,7 +2585,7 @@ SPEC CHECKSUMS: react-native-geolocation: 580c86eb531c0aaf7a14bc76fd2983ce47ca58aa react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: 28ccfa09520e7d7e30739480dea4df003493bfe8 - react-native-keyboard-controller: 47c01b0741ae5fc84e53cf282e61cfa5c2edb19b + react-native-keyboard-controller: 87bd777183a9e55c455670c3abbb9974c7c7f77f react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: 02d31de0e08ab043d48f2a1a8baade109d7b6ca5 react-native-pager-view: c7372cab7caef173f7f81d78520fe21f08805020 diff --git a/package-lock.json b/package-lock.json index 2ced5738cca8..887809d84a77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,7 +97,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.12.2", + "react-native-keyboard-controller": "^1.12.6", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -36893,13 +36893,13 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.12.2.tgz", - "integrity": "sha512-10Sy0+neSHGJxOmOxrUJR8TQznnrQ+jTFQtM1PP6YnblNQeAw1eOa+lO6YLGenRr5WuNSMZbks/3Ay0e2yMKLw==", + "version": "1.12.6", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.12.6.tgz", + "integrity": "sha512-TPqgEelsPOjdmsKmV3iGi1+eG3hzcCXVzJo4F54JeDoCOtk/QP5Qwu586RRgAganogs4XULFRRFXLx8u73GoYw==", "peerDependencies": { "react": "*", "react-native": "*", - "react-native-reanimated": ">=2.3.0" + "react-native-reanimated": ">=2.11.0" } }, "node_modules/react-native-launch-arguments": { diff --git a/package.json b/package.json index 81e963e12671..8b667c2c3517 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#bf3ad41a61c4f6f80ed4d497599ef5247a2dd002", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.12.2", + "react-native-keyboard-controller": "^1.12.6", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", From 6b205ccc38710a3594fed8a24f8e5679139840a9 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 26 Jul 2024 17:23:50 +0200 Subject: [PATCH 020/161] lint --- .../BaseAnchorForAttachmentsOnly.tsx | 9 ++++---- src/components/ConfirmContent.tsx | 5 +++- .../HTMLRenderers/ImageRenderer.tsx | 23 +++++++++---------- .../HTMLRenderers/MentionUserRenderer.tsx | 9 ++++---- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index 15fae49f072d..a003181883b2 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -53,11 +53,10 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow }} onPressIn={onPressIn} onPressOut={onPressOut} - onLongPress={ - (event) => - onShowContextMenu(() => - showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)), - ) + onLongPress={(event) => + onShowContextMenu(() => + showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)), + ) } shouldUseHapticsOnLongPress accessibilityLabel={displayName} diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index 721666ee240c..b3bbf891bf06 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -162,7 +162,10 @@ function ConfirmContent({ )} - + {shouldShowDismissIcon && ( diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx index 2d2282cc869a..f20ae7ac46e8 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.tsx @@ -93,18 +93,17 @@ function ImageRenderer({tnode}: ImageRendererProps) { Navigation.navigate(route); } }} - onLongPress={ - (event) => - onShowContextMenu(() => - showContextMenuForReport( - event, - anchor, - report?.reportID ?? '-1', - action, - checkIfContextMenuActive, - ReportUtils.isArchivedRoom(report, reportNameValuePairs), - ), - ) + onLongPress={(event) => + onShowContextMenu(() => + showContextMenuForReport( + event, + anchor, + report?.reportID ?? '-1', + action, + checkIfContextMenuActive, + ReportUtils.isArchivedRoom(report, reportNameValuePairs), + ), + ) } shouldUseHapticsOnLongPress accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index e005212a0755..34a5b5e19298 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -86,11 +86,10 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona {({onShowContextMenu, anchor, report, reportNameValuePairs, action, checkIfContextMenuActive}) => ( - onShowContextMenu(() => - showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)), - ) + onLongPress={(event) => + onShowContextMenu(() => + showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)), + ) } onPress={(event) => { event.preventDefault(); From 8a27af57067bc25b9aa79eb25e271333f6d34a43 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 7 Aug 2024 14:03:20 +0200 Subject: [PATCH 021/161] add more states and actions --- .../ActionSheetAwareScrollViewContext.tsx | 19 ++++++++++++++++++- .../HTMLRenderers/PreRenderer.tsx | 19 ++++++------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx index f592146659f4..f38d00a50a30 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -53,6 +53,7 @@ const Actions = { POPOVER_ANY_ACTION: 'POPOVER_ANY_ACTION', OPEN_EMOJI_PICKER_POPOVER: 'OPEN_EMOJI_PICKER_POPOVER', OPEN_EMOJI_PICKER_POPOVER_STANDALONE: 'OPEN_EMOJI_PICKER_POPOVER_STANDALONE', + CLOSE_EMOJI_PICKER_POPOVER_STANDALONE: 'CLOSE_EMOJI_PICKER_POPOVER_STANDALONE', CLOSE_EMOJI_PICKER_POPOVER: 'CLOSE_EMOJI_PICKER_POPOVER', MEASURE_EMOJI_PICKER_POPOVER: 'MEASURE_EMOJI_PICKER_POPOVER', HIDE_WITHOUT_ANIMATION: 'HIDE_WITHOUT_ANIMATION', @@ -65,7 +66,7 @@ const Actions = { SHOW_ATTACHMENTS_POPOVER: 'SHOW_ATTACHMENTS_POPOVER', CLOSE_ATTACHMENTS_POPOVER: 'CLOSE_ATTACHMENTS_POPOVER', SHOW_ATTACHMENTS_PICKER_POPOVER: 'SHOW_ATTACHMENTS_PICKER_POPOVER', - CLOSE_EMOJI_PICKER_POPOVER_STANDALONE: 'CLOSE_EMOJI_PICKER_POPOVER_STANDALONE', + CLOSE_ATTACHMENTS_PICKER_POPOVER: 'CLOSE_ATTACHMENTS_PICKER_POPOVER', MEASURE_CALL_POPOVER: 'MEASURE_CALL_POPOVER', CLOSE_CALL_POPOVER: 'CLOSE_CALL_POPOVER', }; @@ -88,6 +89,8 @@ const States = { CALL_POPOVER_WITH_KEYBOARD_CLOSED: 'callPopoverWithKeyboardClosed', ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN: 'attachmentsPopoverWithKeyboardOpen', ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED: 'attachmentsPopoverWithKeyboardClosed', + ATTACHMENTS_POPOVER_OPEN: 'attachmentsPopoverOpen', + ATTACHMENTS_POPOVER_CLOSED: 'attachmentsPopoverClosed', MODAL_DELETED: 'modalDeleted', MODAL_WITH_KEYBOARD_OPEN_DELETED: 'modalWithKeyboardOpenDeleted', EDIT_MESSAGE: 'editMessage', @@ -101,6 +104,8 @@ const STATE_MACHINE = { [Actions.MEASURE_COMPOSER]: States.IDLE, [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, [Actions.SHOW_ATTACHMENTS_PICKER_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, + [Actions.OPEN_EMOJI_PICKER_POPOVER_STANDALONE]: States.EMOJI_PICKER_POPOVER_OPEN, + [Actions.SHOW_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_OPEN, }, [States.POPOVER_OPEN]: { [Actions.CLOSE_POPOVER]: States.POPOVER_CLOSED, @@ -118,6 +123,7 @@ const STATE_MACHINE = { [States.EMOJI_PICKER_POPOVER_OPEN]: { [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, [Actions.CLOSE_EMOJI_PICKER_POPOVER]: States.POPOVER_CLOSED, + [Actions.CLOSE_EMOJI_PICKER_POPOVER_STANDALONE]: States.KEYBOARD_POPOVER_CLOSED, }, [States.MODAL_DELETED]: { [Actions.MEASURE_CONFIRM_MODAL]: States.MODAL_DELETED, @@ -169,6 +175,17 @@ const STATE_MACHINE = { [Actions.MEASURE_COMPOSER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED, }, + + [States.ATTACHMENTS_POPOVER_OPEN]: { + [Actions.MEASURE_POPOVER]: States.ATTACHMENTS_POPOVER_OPEN, + [Actions.MEASURE_COMPOSER]: States.ATTACHMENTS_POPOVER_OPEN, + [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_CLOSED, + }, + [States.ATTACHMENTS_POPOVER_CLOSED]: { + [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_CLOSED, + [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, + }, + [States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED]: { [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, }, diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx index 72a89a183ec9..54f1a7ec77b7 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -39,19 +39,12 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d onPress={onPressIn ?? (() => {})} onPressIn={onPressIn} onPressOut={onPressOut} - onLongPress={ - (event) =>console.log(111, 0); - onShowContextMenu(() => - showContextMenuForReport( - event, - anchor, - report?.reportID ?? '-1', - action, - checkIfContextMenuActive, - ReportUtils.isArchivedRoom(report, reportNameValuePairs), - ), - ) - } + onLongPress={(event) => { + console.log(111, 0); + onShowContextMenu(() => + showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)), + ); + }} shouldUseHapticsOnLongPress role={CONST.ROLE.PRESENTATION} accessibilityLabel={translate('accessibilityHints.prestyledText')} From 1123d522734f8363b83f29288887ac6489a7ff46 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 8 Aug 2024 18:09:46 +0200 Subject: [PATCH 022/161] attachment popover and emoji picker without keyboard --- .../ActionSheetKeyboardSpace.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index c2214dea813b..02a1e4e8d8e7 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -47,6 +47,7 @@ const useAnimatedKeyboard = () => { }, onEnd: (e) => { 'worklet'; + state.value = e.height > 0 ? KeyboardState.OPEN : KeyboardState.CLOSED; height.value = e.height; progress.value = e.progress; @@ -152,7 +153,6 @@ function ActionSheetKeyboardSpace(props: ViewProps) { } case States.MODAL_DELETED: - case States.EMOJI_PICKER_POPOVER_OPEN: case States.POPOVER_OPEN: { if (popoverHeight) { if (previousElementOffset !== 0 || elementOffset > previousElementOffset) { @@ -160,13 +160,28 @@ function ActionSheetKeyboardSpace(props: ViewProps) { return withSpring(elementOffset < 0 ? 0 : elementOffset, SPRING_CONFIG); } - console.log(114, Math.max(previousElementOffset, 0), previousElementOffset); + console.log(114, Math.max(previousElementOffset, 0)); return withSpring(Math.max(previousElementOffset, 0), SPRING_CONFIG); } console.log(115, 0); return 0; } + case States.ATTACHMENTS_POPOVER_OPEN: + case States.EMOJI_PICKER_POPOVER_OPEN: { + if (popoverHeight) { + if (previousElementOffset !== 0 || elementOffset > previousElementOffset) { + console.log(98, elementOffset < 0 ? 0 : elementOffset, elementOffset); + return withSpring(elementOffset < 0 ? 0 : elementOffset, SPRING_CONFIG); + } + + console.log(99, Math.max(previousElementOffset, 0), previousElementOffset, popoverHeight - composerHeight); + return withSpring(popoverHeight - composerHeight, SPRING_CONFIG); + } + + console.log(1100, 0); + return 0; + } case States.MODAL_WITH_KEYBOARD_OPEN_DELETED: case States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN: { From 3e7680e77b672f01c9fcf251afe37e74eeb6fe0d Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 8 Aug 2024 18:10:47 +0200 Subject: [PATCH 023/161] EMOJI_PICKER_WITH_KEYBOARD_OPEN --- .../ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index 02a1e4e8d8e7..6dd6296ad0ab 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -252,7 +252,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { } console.log(126, 0); - return 0; + return Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0); } case States.KEYBOARD_POPOVER_CLOSED: { From 6281b7b1caf46c63c2e499d56422f247d41a38d2 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 8 Aug 2024 18:52:29 +0200 Subject: [PATCH 024/161] popover closing --- .../ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index 6dd6296ad0ab..68a0e91caa48 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -262,7 +262,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { } console.log(128, popoverHeight - composerHeight); - return popoverHeight - composerHeight; + return Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0); } case States.KEYBOARD_POPOVER_OPEN: { From b5c697f536bb29ae6cf623152547eb905f832478 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Thu, 8 Aug 2024 19:17:46 +0200 Subject: [PATCH 025/161] state machine --- .../ActionSheetAwareScrollViewContext.tsx | 5 ++--- src/components/EmojiPicker/EmojiPickerButton.tsx | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx index f38d00a50a30..6f0dad2b1b19 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -123,7 +123,7 @@ const STATE_MACHINE = { [States.EMOJI_PICKER_POPOVER_OPEN]: { [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, [Actions.CLOSE_EMOJI_PICKER_POPOVER]: States.POPOVER_CLOSED, - [Actions.CLOSE_EMOJI_PICKER_POPOVER_STANDALONE]: States.KEYBOARD_POPOVER_CLOSED, + [Actions.CLOSE_EMOJI_PICKER_POPOVER_STANDALONE]: States.POPOVER_CLOSED, }, [States.MODAL_DELETED]: { [Actions.MEASURE_CONFIRM_MODAL]: States.MODAL_DELETED, @@ -175,11 +175,10 @@ const STATE_MACHINE = { [Actions.MEASURE_COMPOSER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED, }, - [States.ATTACHMENTS_POPOVER_OPEN]: { [Actions.MEASURE_POPOVER]: States.ATTACHMENTS_POPOVER_OPEN, [Actions.MEASURE_COMPOSER]: States.ATTACHMENTS_POPOVER_OPEN, - [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_CLOSED, + [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.POPOVER_CLOSED, }, [States.ATTACHMENTS_POPOVER_CLOSED]: { [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_CLOSED, diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 40e3cdd45f71..00389e3b9e10 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -31,7 +31,7 @@ type EmojiPickerButtonProps = { }; function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { - const actionSheetContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); + const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); @@ -43,12 +43,12 @@ function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shi return; } - actionSheetContext.transitionActionSheetState({ + actionSheetAwareScrollViewContext.transitionActionSheetState({ type: ActionSheetAwareScrollView.Actions.OPEN_EMOJI_PICKER_POPOVER_STANDALONE, }); const onHide = () => { - actionSheetContext.transitionActionSheetState({ + actionSheetAwareScrollViewContext.transitionActionSheetState({ type: ActionSheetAwareScrollView.Actions.CLOSE_EMOJI_PICKER_POPOVER_STANDALONE, }); From 426a5425df93bf60f46e76447e0d8ffaca9942ac Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Fri, 23 Aug 2024 11:12:10 +0200 Subject: [PATCH 026/161] keyboard-controller bump --- ios/Podfile.lock | 64 +++++++++++++++++++++++------------------------ package-lock.json | 10 ++++---- package.json | 2 +- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 01c4fc376d30..f6f7a0e8946a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -19,12 +19,12 @@ PODS: - Airship/Core - AirshipFrameworkProxy (5.1.1): - Airship (= 17.7.3) - - AirshipServiceExtension (17.8.0) - - AppAuth (1.6.2): - - AppAuth/Core (= 1.6.2) - - AppAuth/ExternalUserAgent (= 1.6.2) - - AppAuth/Core (1.6.2) - - AppAuth/ExternalUserAgent (1.6.2): + - AirshipServiceExtension (18.7.0) + - AppAuth (1.7.5): + - AppAuth/Core (= 1.7.5) + - AppAuth/ExternalUserAgent (= 1.7.5) + - AppAuth/Core (1.7.5) + - AppAuth/ExternalUserAgent (1.7.5): - AppAuth/Core - boost (1.83.0) - BVLinearGradient (2.8.1): @@ -184,44 +184,44 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - - GoogleSignIn (7.0.0): - - AppAuth (~> 1.5) - - GTMAppAuth (< 3.0, >= 1.3) - - GTMSessionFetcher/Core (< 4.0, >= 1.1) - - GoogleUtilities/AppDelegateSwizzler (7.13.0): + - GoogleSignIn (7.1.0): + - AppAuth (< 2.0, >= 1.7.3) + - GTMAppAuth (< 5.0, >= 4.1.1) + - GTMSessionFetcher/Core (~> 3.3) + - GoogleUtilities/AppDelegateSwizzler (7.13.3): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - GoogleUtilities/Privacy - - GoogleUtilities/Environment (7.13.0): + - GoogleUtilities/Environment (7.13.3): - GoogleUtilities/Privacy - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/ISASwizzler (7.13.0): + - GoogleUtilities/ISASwizzler (7.13.3): - GoogleUtilities/Privacy - - GoogleUtilities/Logger (7.13.0): + - GoogleUtilities/Logger (7.13.3): - GoogleUtilities/Environment - GoogleUtilities/Privacy - - GoogleUtilities/MethodSwizzler (7.13.0): + - GoogleUtilities/MethodSwizzler (7.13.3): - GoogleUtilities/Logger - GoogleUtilities/Privacy - - GoogleUtilities/Network (7.13.0): + - GoogleUtilities/Network (7.13.3): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Privacy - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.13.0)": + - "GoogleUtilities/NSData+zlib (7.13.3)": - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (7.13.0) - - GoogleUtilities/Reachability (7.13.0): + - GoogleUtilities/Privacy (7.13.3) + - GoogleUtilities/Reachability (7.13.3): - GoogleUtilities/Logger - GoogleUtilities/Privacy - - GoogleUtilities/UserDefaults (7.13.0): + - GoogleUtilities/UserDefaults (7.13.3): - GoogleUtilities/Logger - GoogleUtilities/Privacy - - GTMAppAuth (2.0.0): - - AppAuth/Core (~> 1.6) - - GTMSessionFetcher/Core (< 4.0, >= 1.5) - - GTMSessionFetcher/Core (3.3.1) + - GTMAppAuth (4.1.1): + - AppAuth/Core (~> 1.7) + - GTMSessionFetcher/Core (< 4.0, >= 3.3) + - GTMSessionFetcher/Core (3.5.0) - hermes-engine (0.73.4): - hermes-engine/Pre-built (= 0.73.4) - hermes-engine/Pre-built (0.73.4) @@ -1303,7 +1303,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-keyboard-controller (1.12.6): + - react-native-keyboard-controller (1.13.2): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2487,8 +2487,8 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Airship: 5a6d3f8a982398940b0d48423bb9b8736717c123 AirshipFrameworkProxy: 7255f4ed9836dc2920f2f1ea5657ced4cee8a35c - AirshipServiceExtension: 0a5fb14c3fd1879355ab05a81d10f64512a4f79c - AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 + AirshipServiceExtension: 7f00d1c36a7deddd435a8ef4a052aa9dbc67d357 + AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa boost: d3f49c53809116a5d38da093a8aa78bf551aed09 BVLinearGradient: 421743791a59d259aec53f4c58793aad031da2ca DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 @@ -2514,10 +2514,10 @@ SPEC CHECKSUMS: glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a - GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842 - GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 - GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae - GTMSessionFetcher: 8a1b34ad97ebe6f909fb8b9b77fba99943007556 + GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db + GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 + GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de + GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 hermes-engine: b2669ce35fc4ac14f523b307aff8896799829fe2 libaom: 144606b1da4b5915a1054383c3a4459ccdb3c661 libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 @@ -2565,7 +2565,7 @@ SPEC CHECKSUMS: react-native-geolocation: 580c86eb531c0aaf7a14bc76fd2983ce47ca58aa react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: 28ccfa09520e7d7e30739480dea4df003493bfe8 - react-native-keyboard-controller: 87bd777183a9e55c455670c3abbb9974c7c7f77f + react-native-keyboard-controller: ed5da3350e5c500d1a250b453de9546e39c4568e react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: 02d31de0e08ab043d48f2a1a8baade109d7b6ca5 react-native-pager-view: ccd4bbf9fc7effaf8f91f8dae43389844d9ef9fa diff --git a/package-lock.json b/package-lock.json index 8312884fd874..bae1d1a548c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,7 +97,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#93399c6410de32966eb57085936ef6951398c2c3", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.12.6", + "react-native-keyboard-controller": "^1.13.2", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -37289,13 +37289,13 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.12.6", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.12.6.tgz", - "integrity": "sha512-TPqgEelsPOjdmsKmV3iGi1+eG3hzcCXVzJo4F54JeDoCOtk/QP5Qwu586RRgAganogs4XULFRRFXLx8u73GoYw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.13.2.tgz", + "integrity": "sha512-FZkxByqboqa2bq2fXtEnD7f78VmKbu5cHjEfubHfV2ZtkGolZ01XTqKkEQ172GvFSjC5iuF1L3h7C4g8R6Xq9Q==", "peerDependencies": { "react": "*", "react-native": "*", - "react-native-reanimated": ">=2.11.0" + "react-native-reanimated": ">=3.0.0" } }, "node_modules/react-native-launch-arguments": { diff --git a/package.json b/package.json index 0e5e5b3a086a..7c287686374a 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#93399c6410de32966eb57085936ef6951398c2c3", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.12.6", + "react-native-keyboard-controller": "^1.13.2", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", From 1b4f2adc03e8b0334c6face08e72ad91a1418d67 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Tue, 3 Sep 2024 14:49:32 +0200 Subject: [PATCH 027/161] fix popover measure --- src/components/PopoverWithMeasuredContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PopoverWithMeasuredContent.tsx b/src/components/PopoverWithMeasuredContent.tsx index ecfb6c9d3ca7..deb7d10a80f2 100644 --- a/src/components/PopoverWithMeasuredContent.tsx +++ b/src/components/PopoverWithMeasuredContent.tsx @@ -103,7 +103,7 @@ function PopoverWithMeasuredContent({ actionSheetAwareScrollViewContext.transitionActionSheetState({ type: ActionSheetAwareScrollView.Actions.MEASURE_POPOVER, payload: { - popoverHeight: Math.floor(popoverHeight), + popoverHeight: Math.floor(height), }, }); } From 3b17fd7788840db0ceea99f285670d1102eb0092 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Tue, 3 Sep 2024 14:50:22 +0200 Subject: [PATCH 028/161] clean patch after RN75 bump --- ...ated+3.8.1+003+concurrent-rn-updates.patch | 79 ------------------- 1 file changed, 79 deletions(-) delete mode 100644 patches/react-native-reanimated+3.8.1+003+concurrent-rn-updates.patch diff --git a/patches/react-native-reanimated+3.8.1+003+concurrent-rn-updates.patch b/patches/react-native-reanimated+3.8.1+003+concurrent-rn-updates.patch deleted file mode 100644 index 02a298f4c5a6..000000000000 --- a/patches/react-native-reanimated+3.8.1+003+concurrent-rn-updates.patch +++ /dev/null @@ -1,79 +0,0 @@ -diff --git a/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.cpp b/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.cpp -index b34579d..87513aa 100644 ---- a/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.cpp -+++ b/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.cpp -@@ -418,6 +418,24 @@ bool NativeReanimatedModule::isThereAnyLayoutProp( - return false; - } - -+jsi::Object NativeReanimatedModule::getUIProps( -+ jsi::Runtime &rt, -+ const jsi::Object &props) { -+ jsi::Object res = jsi::Object(rt); -+ const jsi::Array propNames = props.getPropertyNames(rt); -+ for (size_t i = 0; i < propNames.size(rt); ++i) { -+ const std::string propName = -+ propNames.getValueAtIndex(rt, i).asString(rt).utf8(rt); -+ bool isLayoutProp = -+ nativePropNames_.find(propName) != nativePropNames_.end(); -+ if (!isLayoutProp) { -+ const jsi::Value &propValue = props.getProperty(rt, propName.c_str()); -+ res.setProperty(rt, propName.c_str(), propValue); -+ } -+ } -+ return res; -+} -+ - jsi::Value NativeReanimatedModule::filterNonAnimatableProps( - jsi::Runtime &rt, - const jsi::Value &props) { -@@ -565,13 +583,15 @@ void NativeReanimatedModule::performOperations() { - } - } - -+ // If there's no layout props to be updated, we can apply the updates -+ // directly onto the components and skip the commit. -+ for (const auto &[shadowNode, props] : copiedOperationsQueue) { -+ Tag tag = shadowNode->getTag(); -+ jsi::Object uiProps = getUIProps(rt, props->asObject(rt)); -+ synchronouslyUpdateUIPropsFunction_(rt, tag, uiProps); -+ } -+ - if (!hasLayoutUpdates) { -- // If there's no layout props to be updated, we can apply the updates -- // directly onto the components and skip the commit. -- for (const auto &[shadowNode, props] : copiedOperationsQueue) { -- Tag tag = shadowNode->getTag(); -- synchronouslyUpdateUIPropsFunction_(rt, tag, props->asObject(rt)); -- } - return; - } - -diff --git a/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.h b/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.h -index 9f8c32d..cb31205 100644 ---- a/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.h -+++ b/node_modules/react-native-reanimated/Common/cpp/NativeModules/NativeReanimatedModule.h -@@ -163,6 +163,7 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec { - - #ifdef RCT_NEW_ARCH_ENABLED - bool isThereAnyLayoutProp(jsi::Runtime &rt, const jsi::Object &props); -+ jsi::Object getUIProps(jsi::Runtime &rt, const jsi::Object &props); - jsi::Value filterNonAnimatableProps( - jsi::Runtime &rt, - const jsi::Value &props); -diff --git a/node_modules/react-native-reanimated/apple/REANodesManager.mm b/node_modules/react-native-reanimated/apple/REANodesManager.mm -index ed36c99..0c64925 100644 ---- a/node_modules/react-native-reanimated/apple/REANodesManager.mm -+++ b/node_modules/react-native-reanimated/apple/REANodesManager.mm -@@ -432,9 +432,9 @@ - (void)synchronouslyUpdateViewOnUIThread:(nonnull NSNumber *)viewTag props:(non - REAUIView *componentView = - [componentViewRegistry findComponentViewWithTag:[viewTag integerValue]]; - -- NSSet *propKeysManagedByAnimated = [componentView propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN]; -+ // NSSet *propKeysManagedByAnimated = [componentView propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN]; - [surfacePresenter synchronouslyUpdateViewOnUIThread:viewTag props:uiProps]; -- [componentView setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:propKeysManagedByAnimated]; -+ // [componentView setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:propKeysManagedByAnimated]; - - // `synchronouslyUpdateViewOnUIThread` does not flush props like `backgroundColor` etc. - // so that's why we need to call `finalizeUpdates` here. From f57c4f8ba275f02fbdfd6d2a8ceae6cc842c3f12 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Tue, 3 Sep 2024 15:16:56 +0200 Subject: [PATCH 029/161] bump react-native-keyboard-controller --- ios/Podfile.lock | 4 ++-- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d688213fafb4..b66b25716382 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1689,7 +1689,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-keyboard-controller (1.12.2): + - react-native-keyboard-controller (1.13.3): - DoubleConversion - glog - hermes-engine @@ -3184,7 +3184,7 @@ SPEC CHECKSUMS: react-native-geolocation: b9bd12beaf0ebca61a01514517ca8455bd26fa06 react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: aae312752fcdfaa2240be9a015fc41ce54087546 - react-native-keyboard-controller: 5075321af7b1c834cfb9582230659d032c963278 + react-native-keyboard-controller: ee7d85b59a4555075b5050eab29bda0aadd6791f react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: fb5112b1fa754975485884ae85a3fb6a684f49d5 react-native-pager-view: 6bff9b0883b902571530ddd1b2ea9dc570f321f6 diff --git a/package-lock.json b/package-lock.json index f01231311246..a60f8e009583 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#93399c6410de32966eb57085936ef6951398c2c3", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.13.2", + "react-native-keyboard-controller": "^1.13.3", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -37360,9 +37360,9 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.13.2.tgz", - "integrity": "sha512-FZkxByqboqa2bq2fXtEnD7f78VmKbu5cHjEfubHfV2ZtkGolZ01XTqKkEQ172GvFSjC5iuF1L3h7C4g8R6Xq9Q==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.13.3.tgz", + "integrity": "sha512-C6W0Ta5cCKa58pTL3A8WPFNvDgwc5+Qs3pj4v3Q4Emk1INkUEkJDsWyV7HdR232V/98mLSWai2W/8HlqsOAqhQ==", "peerDependencies": { "react": "*", "react-native": "*", diff --git a/package.json b/package.json index e34f1430e29c..8a47fe4d7de8 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#93399c6410de32966eb57085936ef6951398c2c3", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.13.2", + "react-native-keyboard-controller": "^1.13.3", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", From e042032b043454a80cd5b5340c741b420c96b84d Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Tue, 3 Sep 2024 15:17:26 +0200 Subject: [PATCH 030/161] remove patch react-native-keyboard-controller --- ...keyboard-controller+1.12.2+002+rn-75-fixes.patch | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 patches/react-native-keyboard-controller+1.12.2+002+rn-75-fixes.patch diff --git a/patches/react-native-keyboard-controller+1.12.2+002+rn-75-fixes.patch b/patches/react-native-keyboard-controller+1.12.2+002+rn-75-fixes.patch deleted file mode 100644 index f7ab542a2a2b..000000000000 --- a/patches/react-native-keyboard-controller+1.12.2+002+rn-75-fixes.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt -index 50252f0..28a70d6 100644 ---- a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt -+++ b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt -@@ -13,7 +13,7 @@ val ThemedReactContext.rootView: View? - - fun ThemedReactContext?.dispatchEvent(viewId: Int, event: Event<*>) { - val eventDispatcher: EventDispatcher? = -- UIManagerHelper.getEventDispatcherForReactTag(this, viewId) -+ UIManagerHelper.getEventDispatcherForReactTag(this!!, viewId) - eventDispatcher?.dispatchEvent(event) - } - From c0c2e52886ea8da7cf862a3d8d4fb4e97d3c5299 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 9 Sep 2024 11:50:29 +0200 Subject: [PATCH 031/161] fix firebase traceMap error --- src/libs/Firebase/index.native.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Firebase/index.native.ts b/src/libs/Firebase/index.native.ts index d2746d8b25e7..0af52eefb58c 100644 --- a/src/libs/Firebase/index.native.ts +++ b/src/libs/Firebase/index.native.ts @@ -41,7 +41,7 @@ const stopTrace: StopTrace = (customEventName) => { return; } - const trace = traceMap[customEventName].trace; + const trace = traceMap[customEventName]?.trace; if (!trace) { return; } From 88030516257f020476e377766e2f31ffa17b8b7f Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 11 Sep 2024 10:30:15 +0200 Subject: [PATCH 032/161] bump react-native-keyboard-controller --- ios/Podfile.lock | 4 ++-- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b119e04c3ae5..79cae5374b71 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1689,7 +1689,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-keyboard-controller (1.13.3): + - react-native-keyboard-controller (1.13.4): - DoubleConversion - glog - hermes-engine @@ -3184,7 +3184,7 @@ SPEC CHECKSUMS: react-native-geolocation: b9bd12beaf0ebca61a01514517ca8455bd26fa06 react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 react-native-key-command: aae312752fcdfaa2240be9a015fc41ce54087546 - react-native-keyboard-controller: ee7d85b59a4555075b5050eab29bda0aadd6791f + react-native-keyboard-controller: 56b8c30d8ba0eb27b406eec79799d870d95e9046 react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: fb5112b1fa754975485884ae85a3fb6a684f49d5 react-native-pager-view: 94195f1bf32e7f78359fa20057c97e632364a08b diff --git a/package-lock.json b/package-lock.json index 6139b904e62e..9166c7e16e98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#cb392140db4953a283590d7cf93b4d0461baa2a9", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.13.3", + "react-native-keyboard-controller": "^1.13.4", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", @@ -37352,9 +37352,9 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.13.3.tgz", - "integrity": "sha512-C6W0Ta5cCKa58pTL3A8WPFNvDgwc5+Qs3pj4v3Q4Emk1INkUEkJDsWyV7HdR232V/98mLSWai2W/8HlqsOAqhQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.13.4.tgz", + "integrity": "sha512-80unzD4S+ybgYOluhdeV4zV62ejg6Jt0l5iw7PuA1y3aM1a1+5tS2WoHLNk8605oDaGcVLGNMNF0Qv4GWe97Bg==", "peerDependencies": { "react": "*", "react-native": "*", diff --git a/package.json b/package.json index d2ac50e1dbe5..44e592363302 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#cb392140db4953a283590d7cf93b4d0461baa2a9", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "^1.13.3", + "react-native-keyboard-controller": "^1.13.4", "react-native-launch-arguments": "^4.0.2", "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", From 808d75f277359610b8614970921bbca841c1fa96 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 11 Sep 2024 10:56:20 +0200 Subject: [PATCH 033/161] left only context menu transition: clean components --- .../BaseAnchorForAttachmentsOnly.tsx | 6 ++--- .../AttachmentPicker/index.native.tsx | 3 +-- src/components/AttachmentPicker/types.ts | 6 ----- src/components/ConfirmContent.tsx | 23 +++------------- .../EmojiPicker/EmojiPickerButton.tsx | 16 +---------- .../EmojiPickerMenu/index.native.tsx | 22 ++------------- .../Reactions/AddReactionBubble.tsx | 3 +-- .../QuickEmojiReactions/index.native.tsx | 26 ++---------------- .../ReportActionItemEmojiReactions.tsx | 11 -------- .../report/ContextMenu/ContextMenuActions.tsx | 21 +++------------ .../AttachmentPickerWithMenuItems.tsx | 18 +------------ src/pages/home/report/ReportActionItem.tsx | 27 ------------------- 12 files changed, 16 insertions(+), 166 deletions(-) diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index 6e2ced803501..1d273e847d26 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -41,7 +41,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow return ( - {({onShowContextMenu, anchor, report, reportNameValuePairs, action, checkIfContextMenuActive}) => ( + {({anchor, report, reportNameValuePairs, action, checkIfContextMenuActive}) => ( { @@ -54,9 +54,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow onPressIn={onPressIn} onPressOut={onPressOut} onLongPress={(event) => - onShowContextMenu(() => - showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)), - ) + showContextMenuForReport(event, anchor, report?.reportID ?? '-1', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report, reportNameValuePairs)) } shouldUseHapticsOnLongPress accessibilityLabel={displayName} diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index 68cc7458728c..366366423324 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -112,7 +112,7 @@ const getDataForUpload = (fileData: FileResponse): Promise => { * a callback. This is the ios/android implementation * opening a modal with attachment options */ -function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, shouldHideCameraOption = false, onLayout}: AttachmentPickerProps) { +function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, shouldHideCameraOption = false}: AttachmentPickerProps) { const styles = useThemeStyles(); const [isVisible, setIsVisible] = useState(false); @@ -382,7 +382,6 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s return ( <> { close(); onCanceled.current(); diff --git a/src/components/AttachmentPicker/types.ts b/src/components/AttachmentPicker/types.ts index 7ef0e1120afd..63ed27408eea 100644 --- a/src/components/AttachmentPicker/types.ts +++ b/src/components/AttachmentPicker/types.ts @@ -42,12 +42,6 @@ type AttachmentPickerProps = { /** The types of files that can be selected with this picker. */ type?: ValueOf; - /** - * Optional callback attached to popover's children container. - * Invoked on Popover mount and layout changes. - */ - onLayout?: ((event: LayoutChangeEvent) => void) | undefined; - acceptedFileTypes?: Array>; }; diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index b3bbf891bf06..a75179dd3831 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -1,6 +1,6 @@ import type {ReactNode} from 'react'; -import React, {useCallback, useContext} from 'react'; -import type {LayoutChangeEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import React, {useContext} from 'react'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -132,20 +132,6 @@ function ConfirmContent({ const {isOffline} = useNetwork(); const StyleUtils = useStyleUtils(); - const onLayout = useCallback( - (event: LayoutChangeEvent) => { - const {height} = event.nativeEvent.layout; - - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: Actions.MEASURE_CONFIRM_MODAL, - payload: { - popoverHeight: height, - }, - }); - }, - [actionSheetAwareScrollViewContext], - ); - const isCentered = shouldCenterContent; return ( @@ -162,10 +148,7 @@ function ConfirmContent({ )} - + {shouldShowDismissIcon && ( diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 00389e3b9e10..a0dffa3486ee 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -43,23 +43,9 @@ function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shi return; } - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.OPEN_EMOJI_PICKER_POPOVER_STANDALONE, - }); - - const onHide = () => { - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.CLOSE_EMOJI_PICKER_POPOVER_STANDALONE, - }); - - if (onModalHide) { - onModalHide(); - } - }; - if (!EmojiPickerAction.emojiPickerRef.current?.isEmojiPickerVisible) { EmojiPickerAction.showEmojiPicker( - onHide, + onModalHide, onEmojiSelected, emojiPopoverAnchor, { diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx index 41fac94e9e7d..b5b4c2d7e71c 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx @@ -1,11 +1,9 @@ import type {ListRenderItem} from '@shopify/flash-list'; import lodashDebounce from 'lodash/debounce'; -import React, {useCallback, useContext} from 'react'; +import React, {useCallback} from 'react'; import type {ForwardedRef} from 'react'; -import type {LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; import {runOnUI, scrollTo} from 'react-native-reanimated'; -import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import EmojiPickerMenuItem from '@components/EmojiPicker/EmojiPickerMenuItem'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; @@ -116,25 +114,9 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r }, [styles, windowWidth, preferredSkinTone, singleExecution, onEmojiSelected, translate, activeEmoji], ); - const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); - const onLayout = useCallback( - (event: LayoutChangeEvent) => { - const {height} = event.nativeEvent.layout; - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.MEASURE_EMOJI_PICKER_POPOVER, - payload: { - popoverHeight: height, - }, - }); - }, - [actionSheetAwareScrollViewContext], - ); return ( - + EmojiPickerAction.resetEmojiPopoverAnchor, []); const onPress = () => { - const openPicker = (refParam?: PickerRefElement, anchorOrigin?: AnchorOrigin, onHide = () => {}) => { + const openPicker = (refParam?: PickerRefElement, anchorOrigin?: AnchorOrigin) => { EmojiPickerAction.showEmojiPicker( () => { - onHide(); setIsEmojiPickerActive?.(false); }, (emojiCode, emojiObject) => { diff --git a/src/components/Reactions/QuickEmojiReactions/index.native.tsx b/src/components/Reactions/QuickEmojiReactions/index.native.tsx index 6c55beb9741d..28e2125e1c40 100644 --- a/src/components/Reactions/QuickEmojiReactions/index.native.tsx +++ b/src/components/Reactions/QuickEmojiReactions/index.native.tsx @@ -1,17 +1,10 @@ -import React, {useContext} from 'react'; -import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; +import React from 'react'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import BaseQuickEmojiReactions from './BaseQuickEmojiReactions'; -import type {BaseQuickEmojiReactionsProps, OpenPickerCallback, QuickEmojiReactionsProps} from './types'; +import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; function QuickEmojiReactions({closeContextMenu, onEmojiSelected, ...rest}: QuickEmojiReactionsProps) { - const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); - const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.OPEN_EMOJI_PICKER_POPOVER, - }); - // We first need to close the menu as it's a popover. // The picker is a popover as well and on mobile there can only // be one active popover at a time. @@ -20,28 +13,13 @@ function QuickEmojiReactions({closeContextMenu, onEmojiSelected, ...rest}: Quick // gets closed, before the picker actually opens, we pass the composer // ref as anchor for the emoji picker popover. openPicker?.(ReportActionComposeFocusManager.composerRef); - - openPicker?.(ReportActionComposeFocusManager.composerRef, undefined, () => { - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.CLOSE_EMOJI_PICKER_POPOVER, - }); - }); }); }; - const onEmojiSelectedCallback: BaseQuickEmojiReactionsProps['onEmojiSelected'] = (emoji, emojiReactions) => { - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.CLOSE_EMOJI_PICKER_POPOVER, - }); - - onEmojiSelected(emoji, emojiReactions); - }; - return ( ); diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index 605e7ea4bb77..fe683f51aa15 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -36,15 +36,6 @@ type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & */ toggleReaction: (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => void; - /** - * Function to call when the user presses on the add reaction button. - * This is only called when the user presses on the button, not on the - * reaction bubbles. - * This is optional, because we don't need it everywhere. - * For example in the ReportActionContextMenu we don't need it. - */ - onPressOpenPicker: (openPicker: OpenPickerCallback) => void; - /** We disable reacting with emojis on report actions that have errors */ shouldBlockReactions?: boolean; @@ -89,7 +80,6 @@ function ReportActionItemEmojiReactions({ reportAction, currentUserPersonalDetails, toggleReaction, - onPressOpenPicker, emojiReactions = {}, shouldBlockReactions = false, preferredLocale = CONST.LOCALES.DEFAULT, @@ -181,7 +171,6 @@ function ReportActionItemEmojiReactions({ })} {!shouldBlockReactions && ( type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && ReportUtils.canEditReportAction(reportAction) && !isArchivedRoom && !isChronosReport, - onPress: (closePopover, {reportID, reportAction, draftMessage, transitionActionSheetState}) => { + onPress: (closePopover, {reportID, reportAction, draftMessage}) => { if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { hideContextMenu(false); const childReportID = reportAction?.childReportID ?? '-1'; @@ -255,10 +255,6 @@ const ContextMenuActions: ContextMenuAction[] = [ }; if (closePopover) { - transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.EDIT_REPORT, - }); - // Hide popover, then call editAction hideContextMenu(false, editAction); return; @@ -622,21 +618,10 @@ const ContextMenuActions: ContextMenuAction[] = [ !isArchivedRoom && !isChronosReport && !ReportActionsUtils.isMessageDeleted(reportAction), - onPress: (closePopover, {reportID, reportAction, transitionActionSheetState}) => { + onPress: (closePopover, {reportID, reportAction}) => { if (closePopover) { - transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.SHOW_DELETE_CONFIRM_MODAL, - }); - - const onClose = () => { - transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.CLOSE_CONFIRM_MODAL, - }); - clearActiveReportAction(); - }; - // Hide popover, then call showDeleteConfirmModal - hideContextMenu(false, () => showDeleteModal(reportID, reportAction, true, onClose, onClose)); + hideContextMenu(false, () => showDeleteModal(reportID, reportAction)); return; } diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx index dd394a65911f..6e3c3a48de74 100644 --- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx +++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx @@ -1,10 +1,8 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {useCallback, useContext, useEffect, useMemo} from 'react'; -import type {LayoutChangeEvent} from 'react-native'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import type {FileObject} from '@components/AttachmentModal'; import AttachmentPicker from '@components/AttachmentPicker'; import Icon from '@components/Icon'; @@ -117,7 +115,6 @@ function AttachmentPickerWithMenuItems({ actionButtonRef, raiseIsScrollLikelyLayoutTriggered, }: AttachmentPickerWithMenuItemsProps) { - const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const isFocused = useIsFocused(); const theme = useTheme(); const styles = useThemeStyles(); @@ -188,18 +185,6 @@ function AttachmentPickerWithMenuItems({ ]; }, [report, reportID, translate]); - const measurePopover = useCallback( - ({nativeEvent}: LayoutChangeEvent) => { - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.MEASURE_POPOVER, - payload: { - popoverHeight: nativeEvent.layout.height, - }, - }); - }, - [actionSheetAwareScrollViewContext], - ); - const onPopoverMenuClose = () => { setMenuVisibility(false); onMenuClosed(); @@ -322,7 +307,6 @@ function AttachmentPickerWithMenuItems({ { - if (!(popoverAnchorRef.current && 'measureInWindow' in popoverAnchorRef.current)) { - return; - } - - // eslint-disable-next-line @typescript-eslint/naming-convention - popoverAnchorRef.current.measureInWindow((_fx, fy, _width, height) => { - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.OPEN_EMOJI_PICKER_POPOVER, - payload: { - fy, - height, - }, - }); - - openPicker(undefined, undefined, () => { - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: ActionSheetAwareScrollView.Actions.CLOSE_EMOJI_PICKER_POPOVER, - }); - }); - }); - }, - [actionSheetAwareScrollViewContext], - ); - const handleShowContextMenu = useCallback( (callback: () => void) => { if (!(popoverAnchorRef.current && 'measureInWindow' in popoverAnchorRef.current)) { @@ -819,7 +793,6 @@ function ReportActionItem({ {!ReportActionsUtils.isMessageDeleted(action) && ( Date: Wed, 11 Sep 2024 11:23:35 +0200 Subject: [PATCH 034/161] left only context menu transition: clean actions --- .../ActionSheetAwareScrollViewContext.tsx | 82 ------------ .../ActionSheetKeyboardSpace.tsx | 126 ++---------------- 2 files changed, 9 insertions(+), 199 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx index 6f0dad2b1b19..ded1719fcb35 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -51,22 +51,9 @@ const Actions = { MEASURE_POPOVER: 'MEASURE_POPOVER', MEASURE_COMPOSER: 'MEASURE_COMPOSER', POPOVER_ANY_ACTION: 'POPOVER_ANY_ACTION', - OPEN_EMOJI_PICKER_POPOVER: 'OPEN_EMOJI_PICKER_POPOVER', - OPEN_EMOJI_PICKER_POPOVER_STANDALONE: 'OPEN_EMOJI_PICKER_POPOVER_STANDALONE', - CLOSE_EMOJI_PICKER_POPOVER_STANDALONE: 'CLOSE_EMOJI_PICKER_POPOVER_STANDALONE', - CLOSE_EMOJI_PICKER_POPOVER: 'CLOSE_EMOJI_PICKER_POPOVER', - MEASURE_EMOJI_PICKER_POPOVER: 'MEASURE_EMOJI_PICKER_POPOVER', HIDE_WITHOUT_ANIMATION: 'HIDE_WITHOUT_ANIMATION', - EDIT_REPORT: 'EDIT_REPORT', - SHOW_DELETE_CONFIRM_MODAL: 'SHOW_DELETE_CONFIRM_MODAL', END_TRANSITION: 'END_TRANSITION', OPEN_CALL_POPOVER: 'OPEN_CALL_POPOVER', - CLOSE_CONFIRM_MODAL: 'CLOSE_CONFIRM_MODAL', - MEASURE_CONFIRM_MODAL: 'MEASURE_CONFIRM_MODAL', - SHOW_ATTACHMENTS_POPOVER: 'SHOW_ATTACHMENTS_POPOVER', - CLOSE_ATTACHMENTS_POPOVER: 'CLOSE_ATTACHMENTS_POPOVER', - SHOW_ATTACHMENTS_PICKER_POPOVER: 'SHOW_ATTACHMENTS_PICKER_POPOVER', - CLOSE_ATTACHMENTS_PICKER_POPOVER: 'CLOSE_ATTACHMENTS_PICKER_POPOVER', MEASURE_CALL_POPOVER: 'MEASURE_CALL_POPOVER', CLOSE_CALL_POPOVER: 'CLOSE_CALL_POPOVER', }; @@ -80,20 +67,9 @@ const States = { KEYBOARD_POPOVER_OPEN: 'keyboardPopoverOpen', KEYBOARD_CLOSED_POPOVER: 'keyboardClosingPopover', POPOVER_MEASURED: 'popoverMeasured', - EMOJI_PICKER_POPOVER_OPEN: 'emojiPickerPopoverOpen', - DELETE_MODAL_OPEN: 'deleteModalOpen', - DELETE_MODAL_WITH_KEYBOARD_OPEN: 'deleteModalWithKeyboardOpen', - EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN: 'emojiPickerPopoverWithKeyboardOpen', - EMOJI_PICKER_WITH_KEYBOARD_OPEN: 'emojiPickerWithKeyboardOpen', CALL_POPOVER_WITH_KEYBOARD_OPEN: 'callPopoverWithKeyboardOpen', CALL_POPOVER_WITH_KEYBOARD_CLOSED: 'callPopoverWithKeyboardClosed', - ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN: 'attachmentsPopoverWithKeyboardOpen', - ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED: 'attachmentsPopoverWithKeyboardClosed', - ATTACHMENTS_POPOVER_OPEN: 'attachmentsPopoverOpen', - ATTACHMENTS_POPOVER_CLOSED: 'attachmentsPopoverClosed', - MODAL_DELETED: 'modalDeleted', MODAL_WITH_KEYBOARD_OPEN_DELETED: 'modalWithKeyboardOpenDeleted', - EDIT_MESSAGE: 'editMessage', }; const STATE_MACHINE = { @@ -102,65 +78,28 @@ const STATE_MACHINE = { [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, [Actions.MEASURE_POPOVER]: States.IDLE, [Actions.MEASURE_COMPOSER]: States.IDLE, - [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, - [Actions.SHOW_ATTACHMENTS_PICKER_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, - [Actions.OPEN_EMOJI_PICKER_POPOVER_STANDALONE]: States.EMOJI_PICKER_POPOVER_OPEN, - [Actions.SHOW_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_OPEN, }, [States.POPOVER_OPEN]: { [Actions.CLOSE_POPOVER]: States.POPOVER_CLOSED, [Actions.MEASURE_POPOVER]: States.POPOVER_OPEN, [Actions.MEASURE_COMPOSER]: States.POPOVER_OPEN, - [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, [Actions.POPOVER_ANY_ACTION]: States.POPOVER_CLOSED, [Actions.HIDE_WITHOUT_ANIMATION]: States.IDLE, - [Actions.EDIT_REPORT]: States.IDLE, - [Actions.SHOW_DELETE_CONFIRM_MODAL]: States.MODAL_DELETED, }, [States.POPOVER_CLOSED]: { [Actions.END_TRANSITION]: States.IDLE, }, - [States.EMOJI_PICKER_POPOVER_OPEN]: { - [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, - [Actions.CLOSE_EMOJI_PICKER_POPOVER]: States.POPOVER_CLOSED, - [Actions.CLOSE_EMOJI_PICKER_POPOVER_STANDALONE]: States.POPOVER_CLOSED, - }, - [States.MODAL_DELETED]: { - [Actions.MEASURE_CONFIRM_MODAL]: States.MODAL_DELETED, - [Actions.CLOSE_CONFIRM_MODAL]: States.POPOVER_CLOSED, - }, [States.KEYBOARD_OPEN]: { [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, [Actions.OPEN_POPOVER]: States.KEYBOARD_POPOVER_OPEN, - [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.KEYBOARD_POPOVER_OPEN, - [Actions.OPEN_EMOJI_PICKER_POPOVER_STANDALONE]: States.EMOJI_PICKER_WITH_KEYBOARD_OPEN, [Actions.CLOSE_KEYBOARD]: States.IDLE, [Actions.OPEN_CALL_POPOVER]: States.CALL_POPOVER_WITH_KEYBOARD_OPEN, - [Actions.SHOW_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, - [Actions.SHOW_ATTACHMENTS_PICKER_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, [Actions.MEASURE_COMPOSER]: States.KEYBOARD_OPEN, }, [States.KEYBOARD_POPOVER_OPEN]: { [Actions.MEASURE_POPOVER]: States.KEYBOARD_POPOVER_OPEN, [Actions.MEASURE_COMPOSER]: States.KEYBOARD_POPOVER_OPEN, [Actions.CLOSE_POPOVER]: States.KEYBOARD_CLOSED_POPOVER, - [Actions.CLOSE_EMOJI_PICKER_POPOVER]: States.KEYBOARD_CLOSED_POPOVER, - [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.KEYBOARD_POPOVER_OPEN, - [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN, - [Actions.SHOW_DELETE_CONFIRM_MODAL]: States.MODAL_WITH_KEYBOARD_OPEN_DELETED, - [Actions.EDIT_REPORT]: States.EDIT_MESSAGE, - }, - [States.MODAL_WITH_KEYBOARD_OPEN_DELETED]: { - [Actions.MEASURE_CONFIRM_MODAL]: States.MODAL_WITH_KEYBOARD_OPEN_DELETED, - [Actions.CLOSE_CONFIRM_MODAL]: States.KEYBOARD_CLOSED_POPOVER, - }, - [States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN]: { - [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN, - [Actions.CLOSE_EMOJI_PICKER_POPOVER]: States.KEYBOARD_CLOSED_POPOVER, - }, - [States.EMOJI_PICKER_WITH_KEYBOARD_OPEN]: { - [Actions.MEASURE_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_WITH_KEYBOARD_OPEN, - [Actions.CLOSE_EMOJI_PICKER_POPOVER_STANDALONE]: States.KEYBOARD_POPOVER_CLOSED, }, [States.CALL_POPOVER_WITH_KEYBOARD_OPEN]: { [Actions.MEASURE_POPOVER]: States.CALL_POPOVER_WITH_KEYBOARD_OPEN, @@ -170,24 +109,6 @@ const STATE_MACHINE = { [States.CALL_POPOVER_WITH_KEYBOARD_CLOSED]: { [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, }, - [States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN]: { - [Actions.MEASURE_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, - [Actions.MEASURE_COMPOSER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN, - [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED, - }, - [States.ATTACHMENTS_POPOVER_OPEN]: { - [Actions.MEASURE_POPOVER]: States.ATTACHMENTS_POPOVER_OPEN, - [Actions.MEASURE_COMPOSER]: States.ATTACHMENTS_POPOVER_OPEN, - [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.POPOVER_CLOSED, - }, - [States.ATTACHMENTS_POPOVER_CLOSED]: { - [Actions.CLOSE_ATTACHMENTS_POPOVER]: States.ATTACHMENTS_POPOVER_CLOSED, - [Actions.OPEN_EMOJI_PICKER_POPOVER]: States.EMOJI_PICKER_POPOVER_OPEN, - }, - - [States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED]: { - [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, - }, [States.KEYBOARD_POPOVER_CLOSED]: { [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, }, @@ -195,9 +116,6 @@ const STATE_MACHINE = { [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, [Actions.END_TRANSITION]: States.KEYBOARD_OPEN, }, - [States.EDIT_MESSAGE]: { - [Actions.CLOSE_KEYBOARD]: States.IDLE, - }, }; function ActionSheetAwareScrollViewProvider(props: PropsWithChildren) { diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index 68a0e91caa48..e69732ef410a 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -32,7 +32,6 @@ const useAnimatedKeyboard = () => { { onStart: (e) => { 'worklet'; - // save the last keyboard height if (e.height === 0) { heightWhenOpened.value = height.value; @@ -47,7 +46,6 @@ const useAnimatedKeyboard = () => { }, onEnd: (e) => { 'worklet'; - state.value = e.height > 0 ? KeyboardState.OPEN : KeyboardState.CLOSED; height.value = e.height; progress.value = e.progress; @@ -59,12 +57,6 @@ const useAnimatedKeyboard = () => { return {state, height, heightWhenOpened, progress}; }; -const setInitialValueAndRunAnimation = (value: number, animation: number) => { - 'worklet'; - - return withSequence(withTiming(value, {duration: 0}), animation); -}; - const useSafeAreaPaddings = () => { const StyleUtils = useStyleUtils(); const insets = useSafeAreaInsets(); @@ -95,10 +87,11 @@ function ActionSheetKeyboardSpace(props: ViewProps) { useAnimatedReaction( () => keyboard.state.value, (lastState) => { - if (lastState === syncLocalWorkletState.lastState) { - return; - } - + if (lastState === syncLocalWorkletState.lastState) { + return; + } + syncLocalWorkletState.lastState = lastState; + if (lastState === KeyboardState.OPEN) { runOnJS(transitionActionSheetState)({type: Actions.OPEN_KEYBOARD}); } else if (lastState === KeyboardState.CLOSED) { @@ -132,14 +125,15 @@ function ActionSheetKeyboardSpace(props: ViewProps) { // Depending on the current and sometimes previous state we can return // either animation or just a value - switch (current.state) { case States.KEYBOARD_OPEN: { - if (previous.state === States.KEYBOARD_CLOSED_POPOVER || (previous.state === States.KEYBOARD_OPEN && elementOffset < 0)) { + if (previous.state === States.KEYBOARD_CLOSED_POPOVER || (previous.state === States.KEYBOARD_OPEN && elementOffset < 0)) { + console.log(110, Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0) + Math.max(elementOffset, 0)); + console.log(1101, previous.state === States.KEYBOARD_CLOSED_POPOVER, previous.state === States.KEYBOARD_OPEN, elementOffset); return Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0) + Math.max(elementOffset, 0); } - console.log(111, 0); + console.log(111, 0, previous.state === States.KEYBOARD_CLOSED_POPOVER, previous.state === States.KEYBOARD_OPEN, elementOffset); return withSpring(0, SPRING_CONFIG); } @@ -152,7 +146,6 @@ function ActionSheetKeyboardSpace(props: ViewProps) { }); } - case States.MODAL_DELETED: case States.POPOVER_OPEN: { if (popoverHeight) { if (previousElementOffset !== 0 || elementOffset > previousElementOffset) { @@ -167,103 +160,6 @@ function ActionSheetKeyboardSpace(props: ViewProps) { console.log(115, 0); return 0; } - case States.ATTACHMENTS_POPOVER_OPEN: - case States.EMOJI_PICKER_POPOVER_OPEN: { - if (popoverHeight) { - if (previousElementOffset !== 0 || elementOffset > previousElementOffset) { - console.log(98, elementOffset < 0 ? 0 : elementOffset, elementOffset); - return withSpring(elementOffset < 0 ? 0 : elementOffset, SPRING_CONFIG); - } - - console.log(99, Math.max(previousElementOffset, 0), previousElementOffset, popoverHeight - composerHeight); - return withSpring(popoverHeight - composerHeight, SPRING_CONFIG); - } - - console.log(1100, 0); - return 0; - } - - case States.MODAL_WITH_KEYBOARD_OPEN_DELETED: - case States.EMOJI_PICKER_POPOVER_WITH_KEYBOARD_OPEN: { - // when item is higher than keyboard and bottom sheet - // we should just stay in place - if (elementOffset < 0) { - console.log(116, invertedKeyboardHeight); - return invertedKeyboardHeight; - } - - const nextOffset = invertedKeyboardHeight + elementOffset; - if (previous?.payload?.popoverHeight !== popoverHeight) { - const previousOffset = invertedKeyboardHeight + previousElementOffset; - - if (previousElementOffset === 0 || nextOffset > previousOffset) { - console.log(117, nextOffset); - return withSpring(nextOffset, SPRING_CONFIG); - } - - console.log(118, previousOffset); - return previousOffset; - } - console.log(119, nextOffset); - return nextOffset; - } - - case States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_CLOSED: - case States.ATTACHMENTS_POPOVER_WITH_KEYBOARD_OPEN: { - // this transition is extremely slow and we may not have `popoverHeight` when keyboard is hiding - // so we run two fold animation: - // - when keyboard is hiding -> we return `0` and thus the content is sticky to composer - // - when keyboard is closed and we have `popoverHeight` (i. e. popup was measured) -> we run spring animation - if (keyboard.state.value === KeyboardState.CLOSING) { - console.log(1200, 0); - return 0; - } - if (keyboard.progress.value === 0) { - console.log(1201, keyboard.progress.value, interpolate(keyboard.progress.value, [0, 1], [popoverHeight - composerHeight, 0]), popoverHeight, composerHeight); - return withSpring(popoverHeight - composerHeight, SPRING_CONFIG); - } - - // when keyboard appears -> we already have all values so we do interpolation based on keyboard position - console.log(1202, keyboard.progress.value, interpolate(keyboard.progress.value, [0, 1], [popoverHeight - composerHeight, 0]), popoverHeight, composerHeight); - return interpolate(keyboard.progress.value, [0, 1], [popoverHeight - composerHeight, 0]); - } - case States.CALL_POPOVER_WITH_KEYBOARD_OPEN: { - if (keyboard.height.value > 0) { - console.log(121, 0); - return 0; - } - console.log(122, lastKeyboardHeight, popoverHeight - composerHeight); - return setInitialValueAndRunAnimation(lastKeyboardHeight, withSpring(popoverHeight - composerHeight, SPRING_CONFIG)); - } - case States.CALL_POPOVER_WITH_KEYBOARD_CLOSED: { - // keyboard is opened - if (keyboard.height.value > 0) { - console.log(123, 0); - return 0; - } - - console.log(124, lastKeyboardHeight); - return withSpring(lastKeyboardHeight, SPRING_CONFIG); - } - case States.EMOJI_PICKER_WITH_KEYBOARD_OPEN: { - if (keyboard.state.value === KeyboardState.CLOSED) { - console.log(125, popoverHeight - composerHeight); - return popoverHeight - composerHeight; - } - - console.log(126, 0); - return Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0); - } - - case States.KEYBOARD_POPOVER_CLOSED: { - if (keyboard.heightWhenOpened.value === keyboard.height.value) { - console.log(127, 0); - return 0; - } - - console.log(128, popoverHeight - composerHeight); - return Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0); - } case States.KEYBOARD_POPOVER_OPEN: { if (keyboard.state.value === KeyboardState.OPEN) { @@ -310,10 +206,6 @@ function ActionSheetKeyboardSpace(props: ViewProps) { duration: 0, }); } - case States.EDIT_MESSAGE: { - console.log(137, 0); - return 0; - } default: console.log(138, 0); From 2427bbe33e3a07cfeea8672cbc171ded8a3c304d Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Wed, 11 Sep 2024 11:32:40 +0200 Subject: [PATCH 035/161] left only context menu transition: the rest --- src/components/ConfirmContent.tsx | 4 +--- src/components/EmojiPicker/EmojiPickerButton.tsx | 4 +--- src/components/Reactions/QuickEmojiReactions/index.native.tsx | 2 +- src/components/Reactions/QuickEmojiReactions/types.ts | 2 +- src/components/Reactions/ReportActionItemEmojiReactions.tsx | 1 - .../home/report/ReportActionCompose/ReportActionCompose.tsx | 1 - 6 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index a75179dd3831..36f24c2a3477 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -1,5 +1,5 @@ import type {ReactNode} from 'react'; -import React, {useContext} from 'react'; +import React from 'react'; import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; @@ -11,7 +11,6 @@ import colors from '@styles/theme/colors'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; -import {Actions, ActionSheetAwareScrollViewContext} from './ActionSheetAwareScrollView'; import Button from './Button'; import Header from './Header'; import Icon from './Icon'; @@ -125,7 +124,6 @@ function ConfirmContent({ titleContainerStyles, shouldReverseStackedButtons = false, }: ConfirmContentProps) { - const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollViewContext); const styles = useThemeStyles(); const {translate} = useLocalize(); const theme = useTheme(); diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index a0dffa3486ee..466cfe0dffd5 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -1,6 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {memo, useContext, useEffect, useRef} from 'react'; -import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; +import React, {memo, useEffect, useRef} from 'react'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; @@ -31,7 +30,6 @@ type EmojiPickerButtonProps = { }; function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shiftVertical = 0, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { - const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); diff --git a/src/components/Reactions/QuickEmojiReactions/index.native.tsx b/src/components/Reactions/QuickEmojiReactions/index.native.tsx index 28e2125e1c40..b0eb88b31b68 100644 --- a/src/components/Reactions/QuickEmojiReactions/index.native.tsx +++ b/src/components/Reactions/QuickEmojiReactions/index.native.tsx @@ -3,7 +3,7 @@ import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManag import BaseQuickEmojiReactions from './BaseQuickEmojiReactions'; import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; -function QuickEmojiReactions({closeContextMenu, onEmojiSelected, ...rest}: QuickEmojiReactionsProps) { +function QuickEmojiReactions({closeContextMenu, ...rest}: QuickEmojiReactionsProps) { const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { // We first need to close the menu as it's a popover. // The picker is a popover as well and on mobile there can only diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts index 725b5aea764f..0021f33ce2c0 100644 --- a/src/components/Reactions/QuickEmojiReactions/types.ts +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -7,7 +7,7 @@ import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx' type PickerRefElement = RefObject; -type OpenPickerCallback = (element?: PickerRefElement, anchorOrigin?: AnchorOrigin, callback?: () => void) => void; +type OpenPickerCallback = (element?: PickerRefElement, anchorOrigin?: AnchorOrigin) => void; type CloseContextMenuCallback = () => void; diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index fe683f51aa15..943158607db4 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -16,7 +16,6 @@ import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx' import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import AddReactionBubble from './AddReactionBubble'; import EmojiReactionBubble from './EmojiReactionBubble'; -import type {OpenPickerCallback} from './QuickEmojiReactions/types'; import ReactionTooltipContent from './ReactionTooltipContent'; type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & { diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index cc4603bbd166..cae2dbeece33 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -146,7 +146,6 @@ function ReportActionCompose({ const actionButtonRef = useRef(null); const {isSmallScreenWidth, isMediumScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const {isOffline} = useNetwork(); - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const navigation = useNavigation(); From 195d6bee112548ba8a2ecde8e9c1c1cb76195838 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 16 Sep 2024 17:40:47 +0200 Subject: [PATCH 036/161] adjust ActionSheetKeyboardSpace --- .../ActionSheetKeyboardSpace.tsx | 68 ++++++++----------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index e69732ef410a..2667bf3a8260 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -1,7 +1,7 @@ -import React, {useContext, useEffect, useRef} from 'react'; +import React, {useContext, useEffect} from 'react'; import type {ViewProps} from 'react-native'; import {useKeyboardHandler} from 'react-native-keyboard-controller'; -import Reanimated, {interpolate, runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withSequence, withSpring, withTiming} from 'react-native-reanimated'; +import Reanimated, {runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring, withTiming} from 'react-native-reanimated'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -25,7 +25,7 @@ const SPRING_CONFIG = { const useAnimatedKeyboard = () => { const state = useSharedValue(KeyboardState.UNKNOWN); const height = useSharedValue(0); - const progress = useSharedValue(0); + const lastHeight = useSharedValue(0); const heightWhenOpened = useSharedValue(0); useKeyboardHandler( @@ -33,28 +33,28 @@ const useAnimatedKeyboard = () => { onStart: (e) => { 'worklet'; // save the last keyboard height - if (e.height === 0) { - heightWhenOpened.value = height.value; + if (e.height !== 0) { + heightWhenOpened.value = e.height; + height.value = 0; } + height.value = heightWhenOpened.value; + lastHeight.value = e.height; state.value = e.height > 0 ? KeyboardState.OPENING : KeyboardState.CLOSING; }, onMove: (e) => { 'worklet'; - - progress.value = e.progress; height.value = e.height; }, onEnd: (e) => { 'worklet'; state.value = e.height > 0 ? KeyboardState.OPEN : KeyboardState.CLOSED; height.value = e.height; - progress.value = e.progress; }, }, [], ); - return {state, height, heightWhenOpened, progress}; + return {state, height, heightWhenOpened}; }; const useSafeAreaPaddings = () => { @@ -71,27 +71,24 @@ function ActionSheetKeyboardSpace(props: ViewProps) { const keyboard = useAnimatedKeyboard(); // similar to using `global` in worklet but it's just a local object - const syncLocalWorkletState = useRef({ - lastState: KeyboardState.UNKNOWN, - }).current; + const syncLocalWorkletStateL = useSharedValue(KeyboardState.UNKNOWN); const {windowHeight} = useWindowDimensions(); const {currentActionSheetState, transitionActionSheetStateWorklet: transition, transitionActionSheetState, resetStateMachine} = useContext(ActionSheetAwareScrollViewContext); // Reset state machine when component unmounts - useEffect(() => () => resetStateMachine(), [resetStateMachine]); // eslint-disable-next-line arrow-body-style - // useEffect(() => { - // return () => resetStateMachine(); - // }, [resetStateMachine]); + useEffect(() => { + return () => resetStateMachine(); + }, [resetStateMachine]); useAnimatedReaction( () => keyboard.state.value, (lastState) => { - if (lastState === syncLocalWorkletState.lastState) { - return; - } - syncLocalWorkletState.lastState = lastState; - + if (lastState === syncLocalWorkletStateL.lastState) { + return; + } + syncLocalWorkletStateL.value = lastState; + if (lastState === KeyboardState.OPEN) { runOnJS(transitionActionSheetState)({type: Actions.OPEN_KEYBOARD}); } else if (lastState === KeyboardState.CLOSED) { @@ -123,22 +120,23 @@ function ActionSheetKeyboardSpace(props: ViewProps) { ? previousPayload.fy + safeArea.top + previousPayload.height - (windowHeight - previousPayload.popoverHeight) : 0; + const isOpeningKeyboard = syncLocalWorkletStateL.value === 1; + const isClosingKeyboard = syncLocalWorkletStateL.value === 3; + const isClosedKeyboard = syncLocalWorkletStateL.value === 4; // Depending on the current and sometimes previous state we can return // either animation or just a value switch (current.state) { case States.KEYBOARD_OPEN: { - if (previous.state === States.KEYBOARD_CLOSED_POPOVER || (previous.state === States.KEYBOARD_OPEN && elementOffset < 0)) { - console.log(110, Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0) + Math.max(elementOffset, 0)); - console.log(1101, previous.state === States.KEYBOARD_CLOSED_POPOVER, previous.state === States.KEYBOARD_OPEN, elementOffset); + if (isClosedKeyboard || isOpeningKeyboard) { + return lastKeyboardHeight - keyboardHeight; + } + if (previous.state === States.KEYBOARD_CLOSED_POPOVER || (previous.state === States.KEYBOARD_OPEN && elementOffset < 0)) { return Math.max(keyboard.heightWhenOpened.value - keyboard.height.value - safeArea.bottom, 0) + Math.max(elementOffset, 0); } - - console.log(111, 0, previous.state === States.KEYBOARD_CLOSED_POPOVER, previous.state === States.KEYBOARD_OPEN, elementOffset); return withSpring(0, SPRING_CONFIG); } case States.POPOVER_CLOSED: { - console.log(112, 0); return withSpring(0, SPRING_CONFIG, () => { transition({ type: Actions.END_TRANSITION, @@ -149,37 +147,30 @@ function ActionSheetKeyboardSpace(props: ViewProps) { case States.POPOVER_OPEN: { if (popoverHeight) { if (previousElementOffset !== 0 || elementOffset > previousElementOffset) { - console.log(113, elementOffset < 0 ? 0 : elementOffset, elementOffset); return withSpring(elementOffset < 0 ? 0 : elementOffset, SPRING_CONFIG); } - console.log(114, Math.max(previousElementOffset, 0)); return withSpring(Math.max(previousElementOffset, 0), SPRING_CONFIG); } - console.log(115, 0); return 0; } case States.KEYBOARD_POPOVER_OPEN: { if (keyboard.state.value === KeyboardState.OPEN) { - console.log(129, 0); - return 0; + return withSpring(0, SPRING_CONFIG); } const nextOffset = elementOffset + lastKeyboardHeight; if (keyboard.state.value === KeyboardState.CLOSED && nextOffset > invertedKeyboardHeight) { - console.log(130, nextOffset < 0 ? 0 : nextOffset, nextOffset); return withSpring(nextOffset < 0 ? 0 : nextOffset, SPRING_CONFIG); } if (elementOffset < 0) { - console.log(131, lastKeyboardHeight - keyboardHeight); - return lastKeyboardHeight - keyboardHeight; + return isClosingKeyboard ? 0 : lastKeyboardHeight - keyboardHeight; } - console.log(132, lastKeyboardHeight, lastKeyboardHeight - keyboardHeight, keyboardHeight); return lastKeyboardHeight; } @@ -187,28 +178,23 @@ function ActionSheetKeyboardSpace(props: ViewProps) { if (elementOffset < 0) { transition({type: Actions.END_TRANSITION}); - console.log(133, 0); return 0; } if (keyboard.state.value === KeyboardState.CLOSED) { - console.log(134, elementOffset + lastKeyboardHeight); return elementOffset + lastKeyboardHeight; } if (keyboard.height.value > 0) { - console.log(135, keyboard.heightWhenOpened.value - keyboard.height.value + elementOffset, 'elementOffset', elementOffset); return keyboard.heightWhenOpened.value - keyboard.height.value + elementOffset; } - console.log(136, elementOffset + lastKeyboardHeight); return withTiming(elementOffset + lastKeyboardHeight, { duration: 0, }); } default: - console.log(138, 0); return 0; } }, []); From 9e25d7f232055ef17018a7e8ab989c0dc2c5f944 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 16 Sep 2024 17:41:38 +0200 Subject: [PATCH 037/161] clean STATE_MACHINE --- .../ActionSheetAwareScrollViewContext.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx index ded1719fcb35..4f56185d64da 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -98,7 +98,6 @@ const STATE_MACHINE = { }, [States.KEYBOARD_POPOVER_OPEN]: { [Actions.MEASURE_POPOVER]: States.KEYBOARD_POPOVER_OPEN, - [Actions.MEASURE_COMPOSER]: States.KEYBOARD_POPOVER_OPEN, [Actions.CLOSE_POPOVER]: States.KEYBOARD_CLOSED_POPOVER, }, [States.CALL_POPOVER_WITH_KEYBOARD_OPEN]: { From 2eb8595e9e7fa347fbe643aedbb5d557bdb00568 Mon Sep 17 00:00:00 2001 From: Taras Perun Date: Mon, 16 Sep 2024 21:23:31 +0200 Subject: [PATCH 038/161] lint --- .../ActionSheetKeyboardSpace.tsx | 7 ++++++- src/components/AttachmentPicker/types.ts | 1 - src/components/EmojiPicker/EmojiPickerButton.tsx | 2 +- .../HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx | 1 - src/components/PopoverMenu.tsx | 9 +++++---- src/hooks/useWorkletStateMachine.ts | 1 + .../report/ContextMenu/BaseReportActionContextMenu.tsx | 2 +- src/pages/home/report/ContextMenu/ContextMenuActions.tsx | 3 +-- .../report/ReportActionCompose/ReportActionCompose.tsx | 6 ------ src/pages/home/report/ReportActionItem.tsx | 1 - 10 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index 2667bf3a8260..afb4f674438e 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -32,8 +32,10 @@ const useAnimatedKeyboard = () => { { onStart: (e) => { 'worklet'; + // save the last keyboard height if (e.height !== 0) { + // eslint-disable-next-line react-compiler/react-compiler heightWhenOpened.value = e.height; height.value = 0; } @@ -43,10 +45,12 @@ const useAnimatedKeyboard = () => { }, onMove: (e) => { 'worklet'; + height.value = e.height; }, onEnd: (e) => { 'worklet'; + state.value = e.height > 0 ? KeyboardState.OPEN : KeyboardState.CLOSED; height.value = e.height; }, @@ -87,6 +91,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { if (lastState === syncLocalWorkletStateL.lastState) { return; } + // eslint-disable-next-line react-compiler/react-compiler syncLocalWorkletStateL.value = lastState; if (lastState === KeyboardState.OPEN) { @@ -110,7 +115,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { const keyboardHeight = keyboard.height.value === 0 ? 0 : keyboard.height.value - safeArea.bottom; // sometimes we need to know the last keyboard height const lastKeyboardHeight = keyboard.heightWhenOpened.value - safeArea.bottom; - const {popoverHeight = 0, fy, height, composerHeight = 0} = current.payload ?? {}; + const {popoverHeight = 0, fy, height} = current.payload ?? {}; const invertedKeyboardHeight = keyboard.state.value === KeyboardState.CLOSED ? lastKeyboardHeight : 0; const elementOffset = fy !== undefined && height !== undefined && popoverHeight !== undefined ? fy + safeArea.top + height - (windowHeight - popoverHeight) : 0; // when the sate is not idle we know for sure we have previous state diff --git a/src/components/AttachmentPicker/types.ts b/src/components/AttachmentPicker/types.ts index 63ed27408eea..057ec72de27e 100644 --- a/src/components/AttachmentPicker/types.ts +++ b/src/components/AttachmentPicker/types.ts @@ -1,5 +1,4 @@ import type {ReactNode} from 'react'; -import type {LayoutChangeEvent} from 'react-native'; import type {ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; import type CONST from '@src/CONST'; diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 34133b88c11a..4ec90caa2e26 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -40,7 +40,7 @@ function EmojiPickerButton({isDisabled = false, id = '', emojiPickerID = '', shi const {translate} = useLocalize(); const isFocused = useIsFocused(); - const openEmojiPicker = (e) => { + const openEmojiPicker = (e: GestureResponderEvent | KeyboardEvent) => { if (!isFocused) { return; } diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx index 694959d1f9de..b7c428e72f29 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.tsx @@ -40,7 +40,6 @@ function PreRenderer({TDefaultRenderer, onPressIn, onPressOut, onLongPress, ...d onPressIn={onPressIn} onPressOut={onPressOut} onLongPress={(event) => { - console.log(111, 0); onShowContextMenu(() => { if (isDisabled) { return; diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 0f9b29ecd971..b950f9b1e244 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -1,9 +1,8 @@ import lodashIsEqual from 'lodash/isEqual'; import type {RefObject} from 'react'; import React, {useLayoutEffect, useState} from 'react'; -import type {LayoutChangeEvent} from 'react-native'; +import type {LayoutChangeEvent, View} from 'react-native'; import {StyleSheet} from 'react-native'; -import type {View} from 'react-native'; import type {ModalProps} from 'react-native-modal'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; @@ -263,8 +262,10 @@ function PopoverMenu({ restoreFocusType={restoreFocusType} > - + {renderHeaderText()} {enteredSubMenuIndexes.length > 0 && renderBackButtonItem()} {currentMenuItems.map((item, menuIndex) => ( diff --git a/src/hooks/useWorkletStateMachine.ts b/src/hooks/useWorkletStateMachine.ts index b9c02faa7bd1..105814c094eb 100644 --- a/src/hooks/useWorkletStateMachine.ts +++ b/src/hooks/useWorkletStateMachine.ts @@ -146,6 +146,7 @@ function useWorkletStateMachine

(stateMachine: StateMachine, initialState: Sta 'worklet'; log('RESET STATE MACHINE'); + // eslint-disable-next-line react-compiler/react-compiler currentState.value = initialState; }, [currentState, initialState, log]); diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index 72975b8193ea..bc48f0b966a2 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -5,8 +5,8 @@ import {InteractionManager, View} from 'react-native'; // eslint-disable-next-line no-restricted-imports import type {GestureResponderEvent, Text as RNText, View as ViewType} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import {useOnyx, withOnyx} from 'react-native-onyx'; +import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import type {ContextMenuItemHandle} from '@components/ContextMenuItem'; import ContextMenuItem from '@components/ContextMenuItem'; import FocusTrapForModal from '@components/FocusTrap/FocusTrapForModal'; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 9b0a2507b6a6..9dd6ca30f5be 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -6,7 +6,6 @@ import {InteractionManager} from 'react-native'; import type {GestureResponderEvent, Text, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; -import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import * as Expensicons from '@components/Icon/Expensicons'; import MiniQuickEmojiReactions from '@components/Reactions/MiniQuickEmojiReactions'; import QuickEmojiReactions from '@components/Reactions/QuickEmojiReactions'; @@ -34,7 +33,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Beta, Download as DownloadOnyx, OnyxInputOrEntry, ReportAction, ReportActionReactions, Transaction} from '@src/types/onyx'; import type IconAsset from '@src/types/utils/IconAsset'; -import {clearActiveReportAction, hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; +import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; import type {ContextMenuAnchor} from './ReportActionContextMenu'; /** Gets the HTML version of the message in an action */ diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index e6164bf65cc1..25b209786f0f 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -335,12 +335,6 @@ function ReportActionCompose({ [], ); - useEffect(() => { - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: isMenuVisible ? ActionSheetAwareScrollView.Actions.SHOW_ATTACHMENTS_POPOVER : ActionSheetAwareScrollView.Actions.CLOSE_ATTACHMENTS_POPOVER, - }); - }, [actionSheetAwareScrollViewContext, isMenuVisible]); - useEffect(() => { const unsubscribe = navigation.addListener('blur', () => { setShouldHideEducationalTooltip(true); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 8af1fd33c63b..649f6d774eef 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -17,7 +17,6 @@ import KYCWall from '@components/KYCWall'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import type {OpenPickerCallback} from '@components/Reactions/QuickEmojiReactions/types'; import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions'; import RenderHTML from '@components/RenderHTML'; import type {ActionableItem} from '@components/ReportActionItem/ActionableItemButtons'; From c4cb4cb08c0edc598aba0337864473a3b812471f Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 28 Oct 2024 13:46:08 +0100 Subject: [PATCH 039/161] fix: ci checks --- .../ActionSheetKeyboardSpace.tsx | 2 +- src/components/EmojiPicker/EmojiPickerButton.tsx | 6 +++--- .../MoneyRequestConfirmationListFooter.tsx | 1 + src/components/SelectionList/ChatListItem.tsx | 1 + src/pages/TransactionDuplicate/Confirmation.tsx | 1 + src/pages/home/report/ReportActionItem.tsx | 13 ++++++++++++- 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index afb4f674438e..55ad0877f31b 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -88,7 +88,7 @@ function ActionSheetKeyboardSpace(props: ViewProps) { useAnimatedReaction( () => keyboard.state.value, (lastState) => { - if (lastState === syncLocalWorkletStateL.lastState) { + if (lastState === syncLocalWorkletStateL.value) { return; } // eslint-disable-next-line react-compiler/react-compiler diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 6182d10c88bd..9b64467c9716 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -1,8 +1,8 @@ import {useIsFocused} from '@react-navigation/native'; import React, {memo, useEffect, useRef} from 'react'; -import type {GestureResponderEvent} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import type PressableProps from '@components/Pressable/GenericPressable/types'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; import useLocalize from '@hooks/useLocalize'; @@ -20,7 +20,7 @@ type EmojiPickerButtonProps = { emojiPickerID?: string; /** A callback function when the button is pressed */ - onPress?: (event?: GestureResponderEvent | KeyboardEvent) => void; + onPress?: PressableProps['onPress']; /** Emoji popup anchor offset shift vertical */ shiftVertical?: number; @@ -37,7 +37,7 @@ function EmojiPickerButton({isDisabled = false, emojiPickerID = '', shiftVertica const {translate} = useLocalize(); const isFocused = useIsFocused(); - const openEmojiPicker = (e: GestureResponderEvent | KeyboardEvent) => { + const openEmojiPicker: PressableProps['onPress'] = (e) => { if (!isFocused) { return; } diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index dcfe72369651..9f8dc76cf1ca 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -285,6 +285,7 @@ function MoneyRequestConfirmationListFooter({ reportNameValuePairs: undefined, action: undefined, checkIfContextMenuActive: () => {}, + onShowContextMenu: () => {}, isDisabled: true, }), [], diff --git a/src/components/SelectionList/ChatListItem.tsx b/src/components/SelectionList/ChatListItem.tsx index 52b42b0c64dd..03071a45be8f 100644 --- a/src/components/SelectionList/ChatListItem.tsx +++ b/src/components/SelectionList/ChatListItem.tsx @@ -49,6 +49,7 @@ function ChatListItem({ action: undefined, transactionThreadReport: undefined, checkIfContextMenuActive: () => {}, + onShowContextMenu: () => {}, isDisabled: true, }; diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index 87748a9697a7..5a67fc03fff2 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -68,6 +68,7 @@ function Confirmation() { action: reportAction, report, checkIfContextMenuActive: () => {}, + onShowContextMenu: () => {}, reportNameValuePairs: undefined, anchor: null, isDisabled: false, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index f9acddb550cf..1abb35916889 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -391,7 +391,18 @@ function ReportActionItem({ ); }); }, - [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], + [ + draftMessage, + action, + reportID, + toggleContextMenuFromActiveReportAction, + originalReportID, + shouldDisplayContextMenu, + disabledActions, + isArchivedRoom, + isChronosReport, + handleShowContextMenu, + ], ); // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. From 9d271ae485c938de5e3745eef202ba4270ed576d Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 28 Oct 2024 14:02:19 +0100 Subject: [PATCH 040/161] fix: don't mock keyboard-controller twice --- jest/setup.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/jest/setup.ts b/jest/setup.ts index 093435971929..7dbe91c32fda 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -64,9 +64,6 @@ jest.mock('react-native-sound', () => { return SoundMock; }); -// eslint-disable-next-line @typescript-eslint/no-unsafe-return -jest.mock('react-native-keyboard-controller', () => require('react-native-keyboard-controller/jest')); - jest.mock('react-native-share', () => ({ default: jest.fn(), })); From 31ba00b22e78526e6e8d54866e9ed619f98b27fe Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 28 Oct 2024 15:43:47 +0100 Subject: [PATCH 041/161] fix: button jumps in Display Name page --- src/components/ScreenWrapper.tsx | 7 +++++ src/hooks/useSafePaddingBottomStyle.ts | 30 +++++++------------ .../settings/Profile/DisplayNamePage.tsx | 1 + 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/components/ScreenWrapper.tsx b/src/components/ScreenWrapper.tsx index 3645b832ed43..203217b0b1f4 100644 --- a/src/components/ScreenWrapper.tsx +++ b/src/components/ScreenWrapper.tsx @@ -11,6 +11,7 @@ import useInitialDimensions from '@hooks/useInitialWindowDimensions'; import useKeyboardState from '@hooks/useKeyboardState'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import {useSafePaddingBottomValue} from '@hooks/useSafePaddingBottomStyle'; import useTackInputFocus from '@hooks/useTackInputFocus'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -69,6 +70,9 @@ type ScreenWrapperProps = { /** Whether KeyboardAvoidingView should be enabled. Use false for screens where this functionality is not necessary */ shouldEnableKeyboardAvoidingView?: boolean; + /** Whether to remove the safe padding bottom (produced by `useSafePaddingBottomStyle`) when the keyboard is shown */ + shouldRemoveSafePaddingBottomWhenKeyboardShown?: boolean; + /** Whether picker modal avoiding should be enabled. Should be enabled when there's a picker at the bottom of a * scrollable form, gives a subtly better UX if disabled on non-scrollable screens with a submit button */ shouldEnablePickerAvoiding?: boolean; @@ -119,6 +123,7 @@ function ScreenWrapper( includeSafeAreaPaddingBottom = true, shouldEnableKeyboardAvoidingView = true, shouldEnablePickerAvoiding = true, + shouldRemoveSafePaddingBottomWhenKeyboardShown = false, headerGapStyles, children, shouldShowOfflineIndicator = true, @@ -152,6 +157,7 @@ function ScreenWrapper( const {isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const {initialHeight} = useInitialDimensions(); const styles = useThemeStyles(); + const safePaddingBottom = useSafePaddingBottomValue(); const keyboardState = useKeyboardState(); const {isDevelopment} = useEnvironment(); const {isOffline} = useNetwork(); @@ -271,6 +277,7 @@ function ScreenWrapper( style={[styles.w100, styles.h100, {maxHeight}, isAvoidingViewportScroll ? [styles.overflowAuto, styles.overscrollBehaviorContain] : {}]} behavior={keyboardAvoidingViewBehavior} enabled={shouldEnableKeyboardAvoidingView} + keyboardVerticalOffset={shouldRemoveSafePaddingBottomWhenKeyboardShown ? -safePaddingBottom : undefined} > { +const useSafePaddingBottomStyle = (): ViewStyle => { const styles = useThemeStyles(); - const [willKeyboardShow, setWillKeyboardShow] = useState(false); - useEffect(() => { - const keyboardWillShowListener = Keyboard.addListener('keyboardWillShow', () => { - setWillKeyboardShow(true); - }); - const keyboardWillHideListener = Keyboard.addListener('keyboardWillHide', () => { - setWillKeyboardShow(false); - }); - return () => { - keyboardWillShowListener.remove(); - keyboardWillHideListener.remove(); - }; - }, []); - const {paddingBottom} = useStyledSafeAreaInsets(); const extraPaddingBottomStyle = useMemo(() => { // Do not add extra padding at the bottom if the keyboard is open or if there is no safe area bottom padding style. - if (willKeyboardShow || !paddingBottom) { + if (!paddingBottom) { return {}; } return styles.pb5; - }, [willKeyboardShow, paddingBottom, styles.pb5]); + }, [paddingBottom, styles.pb5]); return extraPaddingBottomStyle; }; +const useSafePaddingBottomValue = () => { + const style = useSafePaddingBottomStyle(); + + return style.paddingBottom ?? 0; +}; +export {useSafePaddingBottomValue}; export default useSafePaddingBottomStyle; diff --git a/src/pages/settings/Profile/DisplayNamePage.tsx b/src/pages/settings/Profile/DisplayNamePage.tsx index 90f7ca3abbd6..c26aa3f92793 100644 --- a/src/pages/settings/Profile/DisplayNamePage.tsx +++ b/src/pages/settings/Profile/DisplayNamePage.tsx @@ -71,6 +71,7 @@ function DisplayNamePage({isLoadingApp = true, currentUserPersonalDetails}: Disp return ( From 491c481a0c1d9049615d1f93c9cdb92c6238c3da Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 28 Oct 2024 16:16:55 +0100 Subject: [PATCH 042/161] fix: safe area glitch on workspace selection screen --- src/components/SelectionList/BaseSelectionList.tsx | 3 ++- src/components/SelectionList/types.ts | 3 +++ src/pages/WorkspaceSwitcherPage/index.tsx | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 57423992e43e..ae9242d47084 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -106,6 +106,7 @@ function BaseSelectionList( scrollEventThrottle, contentContainerStyle, shouldHighlightSelectedItem = false, + shouldHandleSafeAreaPaddings = true, }: BaseSelectionListProps, ref: ForwardedRef, ) { @@ -671,7 +672,7 @@ function BaseSelectionList( return ( {({safeAreaPaddingBottomStyle}) => ( - + {shouldShowTextInput && ( = Partial & { /** Whether we highlight all the selected items */ shouldHighlightSelectedItem?: boolean; + + /** Whether safe area paddings should be handled */ + shouldHandleSafeAreaPaddings?: boolean; } & TRightHandSideComponent; type SelectionListHandle = { diff --git a/src/pages/WorkspaceSwitcherPage/index.tsx b/src/pages/WorkspaceSwitcherPage/index.tsx index 221889b80b49..f3e67f73d713 100644 --- a/src/pages/WorkspaceSwitcherPage/index.tsx +++ b/src/pages/WorkspaceSwitcherPage/index.tsx @@ -159,7 +159,7 @@ function WorkspaceSwitcherPage() { return ( {({didScreenTransitionEnd}) => ( <> @@ -196,6 +196,7 @@ function WorkspaceSwitcherPage() { shouldShowListEmptyContent={shouldShowCreateWorkspace} initiallyFocusedOptionKey={activeWorkspaceID ?? CONST.WORKSPACE_SWITCHER.NAME} showLoadingPlaceholder={fetchStatus.status === 'loading' || !didScreenTransitionEnd} + shouldHandleSafeAreaPaddings={false} /> )} From ec57d6f4aae6e825263a6f6d6f83a010ef4896cc Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Mon, 28 Oct 2024 18:09:48 +0100 Subject: [PATCH 043/161] fix: emoji picker transition --- src/components/EmojiPicker/EmojiPickerButton.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/EmojiPicker/EmojiPickerButton.tsx b/src/components/EmojiPicker/EmojiPickerButton.tsx index 9b64467c9716..a10e7d9fd1f3 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.tsx +++ b/src/components/EmojiPicker/EmojiPickerButton.tsx @@ -1,5 +1,6 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {memo, useEffect, useRef} from 'react'; +import React, {memo, useContext, useEffect, useRef} from 'react'; +import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import type PressableProps from '@components/Pressable/GenericPressable/types'; @@ -31,6 +32,7 @@ type EmojiPickerButtonProps = { }; function EmojiPickerButton({isDisabled = false, emojiPickerID = '', shiftVertical = 0, onPress, onModalHide, onEmojiSelected}: EmojiPickerButtonProps) { + const actionSheetContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const emojiPopoverAnchor = useRef(null); @@ -42,6 +44,10 @@ function EmojiPickerButton({isDisabled = false, emojiPickerID = '', shiftVertica return; } + actionSheetContext.transitionActionSheetState({ + type: ActionSheetAwareScrollView.Actions.CLOSE_KEYBOARD, + }); + if (!EmojiPickerAction.emojiPickerRef?.current?.isEmojiPickerVisible) { EmojiPickerAction.showEmojiPicker( onModalHide, From b6073e408b9774078315e7ad07664035de039617 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 29 Oct 2024 14:24:01 +0100 Subject: [PATCH 044/161] fix: random jump when keyboard closes --- .../ActionSheetKeyboardSpace.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx index 55ad0877f31b..47dd173e8e8d 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetKeyboardSpace.tsx @@ -1,7 +1,7 @@ import React, {useContext, useEffect} from 'react'; import type {ViewProps} from 'react-native'; import {useKeyboardHandler} from 'react-native-keyboard-controller'; -import Reanimated, {runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring, withTiming} from 'react-native-reanimated'; +import Reanimated, {useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring, withTiming} from 'react-native-reanimated'; import useSafeAreaInsets from '@hooks/useSafeAreaInsets'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -75,9 +75,9 @@ function ActionSheetKeyboardSpace(props: ViewProps) { const keyboard = useAnimatedKeyboard(); // similar to using `global` in worklet but it's just a local object - const syncLocalWorkletStateL = useSharedValue(KeyboardState.UNKNOWN); + const syncLocalWorkletState = useSharedValue(KeyboardState.UNKNOWN); const {windowHeight} = useWindowDimensions(); - const {currentActionSheetState, transitionActionSheetStateWorklet: transition, transitionActionSheetState, resetStateMachine} = useContext(ActionSheetAwareScrollViewContext); + const {currentActionSheetState, transitionActionSheetStateWorklet: transition, resetStateMachine} = useContext(ActionSheetAwareScrollViewContext); // Reset state machine when component unmounts // eslint-disable-next-line arrow-body-style @@ -88,16 +88,16 @@ function ActionSheetKeyboardSpace(props: ViewProps) { useAnimatedReaction( () => keyboard.state.value, (lastState) => { - if (lastState === syncLocalWorkletStateL.value) { + if (lastState === syncLocalWorkletState.value) { return; } // eslint-disable-next-line react-compiler/react-compiler - syncLocalWorkletStateL.value = lastState; + syncLocalWorkletState.value = lastState; if (lastState === KeyboardState.OPEN) { - runOnJS(transitionActionSheetState)({type: Actions.OPEN_KEYBOARD}); + transition({type: Actions.OPEN_KEYBOARD}); } else if (lastState === KeyboardState.CLOSED) { - runOnJS(transitionActionSheetState)({type: Actions.CLOSE_KEYBOARD}); + transition({type: Actions.CLOSE_KEYBOARD}); } }, [], @@ -125,9 +125,9 @@ function ActionSheetKeyboardSpace(props: ViewProps) { ? previousPayload.fy + safeArea.top + previousPayload.height - (windowHeight - previousPayload.popoverHeight) : 0; - const isOpeningKeyboard = syncLocalWorkletStateL.value === 1; - const isClosingKeyboard = syncLocalWorkletStateL.value === 3; - const isClosedKeyboard = syncLocalWorkletStateL.value === 4; + const isOpeningKeyboard = syncLocalWorkletState.value === 1; + const isClosingKeyboard = syncLocalWorkletState.value === 3; + const isClosedKeyboard = syncLocalWorkletState.value === 4; // Depending on the current and sometimes previous state we can return // either animation or just a value switch (current.state) { From d186f7f68418606610c2c0c6058e5dfb720bada0 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 29 Oct 2024 15:11:46 +0100 Subject: [PATCH 045/161] fix: eslint --- src/pages/WorkspaceSwitcherPage/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/WorkspaceSwitcherPage/index.tsx b/src/pages/WorkspaceSwitcherPage/index.tsx index f3e67f73d713..7775f1f0a635 100644 --- a/src/pages/WorkspaceSwitcherPage/index.tsx +++ b/src/pages/WorkspaceSwitcherPage/index.tsx @@ -159,7 +159,7 @@ function WorkspaceSwitcherPage() { return ( {({didScreenTransitionEnd}) => ( <> From 3ef967ffbeaf08b124ad7a8d9aebb8a41aabad4d Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 31 Oct 2024 14:21:22 +0100 Subject: [PATCH 046/161] fix: bottom sheet avoidance when keyboard is hidden --- src/components/PopoverWithMeasuredContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PopoverWithMeasuredContent.tsx b/src/components/PopoverWithMeasuredContent.tsx index 725f07eb24a7..f032fbd1bbbf 100644 --- a/src/components/PopoverWithMeasuredContent.tsx +++ b/src/components/PopoverWithMeasuredContent.tsx @@ -98,7 +98,7 @@ function PopoverWithMeasuredContent({ // it handles the case when `measurePopover` is called with values like: 192, 192.00003051757812, 192 // if we update it, then animation in `ActionSheetAwareScrollView` may be re-running // and we'll see unsynchronized and junky animation - if (actionSheetAwareScrollViewContext.currentActionSheetState.value.current.payload?.popoverHeight !== Math.floor(popoverHeight)) { + if (actionSheetAwareScrollViewContext.currentActionSheetState.value.current.payload?.popoverHeight !== Math.floor(height) && height !== 0) { actionSheetAwareScrollViewContext.transitionActionSheetState({ type: ActionSheetAwareScrollView.Actions.MEASURE_POPOVER, payload: { From e6de40d7845f2cddcd30f7852aff63982b69687d Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Thu, 31 Oct 2024 17:12:15 +0100 Subject: [PATCH 047/161] fix: random transitions when popover gets closed --- .../ActionSheetAwareScrollViewContext.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx index 4f56185d64da..00b662aaba8c 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -99,6 +99,7 @@ const STATE_MACHINE = { [States.KEYBOARD_POPOVER_OPEN]: { [Actions.MEASURE_POPOVER]: States.KEYBOARD_POPOVER_OPEN, [Actions.CLOSE_POPOVER]: States.KEYBOARD_CLOSED_POPOVER, + [Actions.OPEN_KEYBOARD]: States.KEYBOARD_OPEN, }, [States.CALL_POPOVER_WITH_KEYBOARD_OPEN]: { [Actions.MEASURE_POPOVER]: States.CALL_POPOVER_WITH_KEYBOARD_OPEN, From cb5d8fa9805c0d6749e914d466ea276645de478d Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Tue, 19 Nov 2024 10:52:46 +0100 Subject: [PATCH 048/161] fix: typescript checks --- .../ActionSheetAwareScrollViewContext.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx index 00b662aaba8c..133afbacbd72 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -20,21 +20,24 @@ type Context = { resetStateMachine: () => void; }; +const currentActionSheetStateValue = { + previous: { + state: 'idle', + payload: null, + }, + current: { + state: 'idle', + payload: null, + }, +}; const defaultValue: Context = { currentActionSheetState: { - value: { - previous: { - state: 'idle', - payload: null, - }, - current: { - state: 'idle', - payload: null, - }, - }, + value: currentActionSheetStateValue, addListener: noop, removeListener: noop, modify: noop, + get: () => currentActionSheetStateValue, + set: noop, }, transitionActionSheetState: noop, transitionActionSheetStateWorklet: noop, From dc0c88f86d05f653cd6b67b58e53cf0dc486e5b1 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Wed, 20 Nov 2024 12:28:16 +0100 Subject: [PATCH 049/161] feat: Update corpay fields logic, integrate API --- src/CONST.ts | 1 + src/ONYXKEYS.ts | 4 + .../GetCorpayBankAccountFieldsParams.ts | 8 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/actions/BankAccounts.ts | 148 +----------------- .../NonUSD/BankInfo/BankInfo.tsx | 47 ++---- .../substeps/AccountHolderDetails.tsx | 134 ++++++++++++++++ .../BankInfo/substeps/BankAccountDetails.tsx | 12 +- .../NonUSD/BankInfo/substeps/Confirmation.tsx | 8 +- .../NonUSD/BankInfo/types.ts | 15 +- src/types/onyx/CorpayFields.ts | 42 +++++ src/types/onyx/index.ts | 2 + 13 files changed, 219 insertions(+), 205 deletions(-) create mode 100644 src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts create mode 100644 src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/AccountHolderDetails.tsx create mode 100644 src/types/onyx/CorpayFields.ts diff --git a/src/CONST.ts b/src/CONST.ts index c32248e6dcf3..276fefd81c98 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -629,6 +629,7 @@ const CONST = { HANG_TIGHT: 4, }, }, + BANK_INFO_STEP_ACCOUNT_HOLDER_KEY_PREFIX: 'accountHolder', }, INCORPORATION_TYPES: { LLC: 'LLC', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b4510a2faeed..1bf9a5244625 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -458,6 +458,9 @@ const ONYXKEYS = { /** The user's Concierge reportID */ CONCIERGE_REPORT_ID: 'conciergeReportID', + /* Corpay fieds to be used in the bank account creation setup */ + CORPAY_FIELDS: 'corpayFields', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -1029,6 +1032,7 @@ type OnyxValuesMapping = { [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; [ONYXKEYS.CONCIERGE_REPORT_ID]: string; + [ONYXKEYS.CORPAY_FIELDS]: OnyxTypes.CorpayFields; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts b/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts new file mode 100644 index 000000000000..3e02b57f9e12 --- /dev/null +++ b/src/libs/API/parameters/GetCorpayBankAccountFieldsParams.ts @@ -0,0 +1,8 @@ +type GetCorpayBankAccountFieldsParams = { + countryISO: string; + currency: string; + isWithdrawal: boolean; + isBusinessBankAccount: boolean; +}; + +export default GetCorpayBankAccountFieldsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 681114fd3b08..feb73af644bc 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -29,6 +29,7 @@ export type {default as ExpandURLPreviewParams} from './ExpandURLPreviewParams'; export type {default as GetMissingOnyxMessagesParams} from './GetMissingOnyxMessagesParams'; export type {default as GetNewerActionsParams} from './GetNewerActionsParams'; export type {default as GetOlderActionsParams} from './GetOlderActionsParams'; +export type {default as GetCorpayBankAccountFieldsParams} from './GetCorpayBankAccountFieldsParams'; export type {default as GetPolicyCategoriesParams} from './GetPolicyCategories'; export type {default as GetReportPrivateNoteParams} from './GetReportPrivateNoteParams'; export type {default as GetRouteParams} from './GetRouteParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index bd8a58555617..55da017ff5e2 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -888,6 +888,7 @@ type WriteCommandParameters = { }; const READ_COMMANDS = { + GET_CORPAY_BANK_ACCOUNT_FIELDS: 'GetCorpayBankAccountFields', CONNECT_POLICY_TO_QUICKBOOKS_ONLINE: 'ConnectPolicyToQuickbooksOnline', CONNECT_POLICY_TO_XERO: 'ConnectPolicyToXero', SYNC_POLICY_TO_QUICKBOOKS_ONLINE: 'SyncPolicyToQuickbooksOnline', @@ -967,6 +968,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_PLAID_BANK_ACCOUNT_SELECTOR]: Parameters.OpenPlaidBankAccountSelectorParams; [READ_COMMANDS.GET_OLDER_ACTIONS]: Parameters.GetOlderActionsParams; [READ_COMMANDS.GET_NEWER_ACTIONS]: Parameters.GetNewerActionsParams; + [READ_COMMANDS.GET_CORPAY_BANK_ACCOUNT_FIELDS]: Parameters.GetCorpayBankAccountFieldsParams; [READ_COMMANDS.EXPAND_URL_PREVIEW]: Parameters.ExpandURLPreviewParams; [READ_COMMANDS.GET_REPORT_PRIVATE_NOTE]: Parameters.GetReportPrivateNoteParams; [READ_COMMANDS.OPEN_ROOM_MEMBERS_PAGE]: Parameters.OpenRoomMembersPageParams; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index bac1dba9ec71..4f149a026ddf 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -344,8 +344,6 @@ function validateBankAccount(bankAccountID: number, validateCode: string, policy } function getCorpayBankAccountFields(country: string, currency: string) { - // TODO - Use parameters when API is ready - // eslint-disable-next-line @typescript-eslint/no-unused-vars const parameters = { countryISO: country, currency, @@ -353,151 +351,7 @@ function getCorpayBankAccountFields(country: string, currency: string) { isBusinessBankAccount: true, }; - // return API.read(READ_COMMANDS.GET_CORPAY_BANK_ACCOUNT_FIELDS, parameters); - return { - bankCountry: 'AU', - bankCurrency: 'AUD', - classification: 'Business', - destinationCountry: 'AU', - formFields: [ - { - errorMessage: 'Swift must be less than 12 characters', - id: 'swiftBicCode', - isRequired: false, - isRequiredInValueSet: true, - label: 'Swift Code', - regEx: '^.{0,12}$', - validationRules: [ - { - errorMessage: 'Swift must be less than 12 characters', - regEx: '^.{0,12}$', - }, - { - errorMessage: 'The following characters are not allowed: <,>, "', - regEx: '^[^<>\\x22]*$', - }, - ], - }, - { - errorMessage: 'Beneficiary Bank Name must be less than 250 characters', - id: 'bankName', - isRequired: true, - isRequiredInValueSet: true, - label: 'Bank Name', - regEx: '^.{0,250}$', - validationRules: [ - { - errorMessage: 'Beneficiary Bank Name must be less than 250 characters', - regEx: '^.{0,250}$', - }, - { - errorMessage: 'The following characters are not allowed: <,>, "', - regEx: '^[^<>\\x22]*$', - }, - ], - }, - { - errorMessage: 'City must be less than 100 characters', - id: 'bankCity', - isRequired: true, - isRequiredInValueSet: true, - label: 'Bank City', - regEx: '^.{0,100}$', - validationRules: [ - { - errorMessage: 'City must be less than 100 characters', - regEx: '^.{0,100}$', - }, - { - errorMessage: 'The following characters are not allowed: <,>, "', - regEx: '^[^<>\\x22]*$', - }, - ], - }, - { - errorMessage: 'Bank Address Line 1 must be less than 1000 characters', - id: 'bankAddressLine1', - isRequired: true, - isRequiredInValueSet: true, - label: 'Bank Address', - regEx: '^.{0,1000}$', - validationRules: [ - { - errorMessage: 'Bank Address Line 1 must be less than 1000 characters', - regEx: '^.{0,1000}$', - }, - { - errorMessage: 'The following characters are not allowed: <,>, "', - regEx: '^[^<>\\x22]*$', - }, - ], - }, - { - detailedRule: [ - { - isRequired: true, - value: [ - { - errorMessage: 'Beneficiary Account Number is invalid. Value should be 1 to 50 characters long.', - regEx: '^.{1,50}$', - ruleDescription: '1 to 50 characters', - }, - ], - }, - ], - errorMessage: 'Beneficiary Account Number is invalid. Value should be 1 to 50 characters long.', - id: 'accountNumber', - isRequired: true, - isRequiredInValueSet: true, - label: 'Account Number (iACH)', - regEx: '^.{1,50}$', - validationRules: [ - { - errorMessage: 'Beneficiary Account Number is invalid. Value should be 1 to 50 characters long.', - regEx: '^.{1,50}$', - ruleDescription: '1 to 50 characters', - }, - { - errorMessage: 'The following characters are not allowed: <,>, "', - regEx: '^[^<>\\x22]*$', - }, - ], - }, - { - detailedRule: [ - { - isRequired: true, - value: [ - { - errorMessage: 'BSB Number is invalid. Value should be exactly 6 digits long.', - regEx: '^[0-9]{6}$', - ruleDescription: 'Exactly 6 digits', - }, - ], - }, - ], - errorMessage: 'BSB Number is invalid. Value should be exactly 6 digits long.', - id: 'routingCode', - isRequired: true, - isRequiredInValueSet: true, - label: 'BSB Number', - regEx: '^[0-9]{6}$', - validationRules: [ - { - errorMessage: 'BSB Number is invalid. Value should be exactly 6 digits long.', - regEx: '^[0-9]{6}$', - ruleDescription: 'Exactly 6 digits', - }, - { - errorMessage: 'The following characters are not allowed: <,>, "', - regEx: '^[^<>\\x22]*$', - }, - ], - }, - ], - paymentMethods: ['E'], - preferredMethod: 'E', - }; + return API.read(READ_COMMANDS.GET_CORPAY_BANK_ACCOUNT_FIELDS, parameters); } function clearReimbursementAccount() { diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx index 7c5d853428c5..66cd84593a71 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx @@ -1,17 +1,16 @@ import type {ComponentType} from 'react'; -import React, {useCallback, useEffect, useState} from 'react'; +import React from 'react'; import {useOnyx} from 'react-native-onyx'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; -import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; +import AccountHolderDetails from './substeps/AccountHolderDetails'; import BankAccountDetails from './substeps/BankAccountDetails'; import Confirmation from './substeps/Confirmation'; import UploadStatement from './substeps/UploadStatement'; -import type {BankInfoSubStepProps, CorpayFormField} from './types'; +import type {BankInfoSubStepProps} from './types'; type BankInfoProps = { /** Handles back button press */ @@ -21,17 +20,11 @@ type BankInfoProps = { onSubmit: () => void; }; -const {COUNTRY} = INPUT_IDS.ADDITIONAL_DATA; - -const bodyContent: Array> = [BankAccountDetails, UploadStatement, Confirmation]; - function BankInfo({onBackButtonPress, onSubmit}: BankInfoProps) { const {translate} = useLocalize(); const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); - const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); - const [corpayFields, setCorpayFields] = useState([]); - const country = reimbursementAccountDraft?.[COUNTRY] ?? ''; + const [corpayFields] = useOnyx(ONYXKEYS.CORPAY_FIELDS); const policyID = reimbursementAccount?.achData?.policyID ?? '-1'; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const currency = policy?.outputCurrency ?? ''; @@ -40,6 +33,9 @@ function BankInfo({onBackButtonPress, onSubmit}: BankInfoProps) { onSubmit(); }; + const bodyContent: Array> = + currency !== CONST.CURRENCY.AUD ? [BankAccountDetails, AccountHolderDetails, Confirmation] : [BankAccountDetails, AccountHolderDetails, UploadStatement, Confirmation]; + const { componentToRender: SubStep, isEditing, @@ -48,15 +44,8 @@ function BankInfo({onBackButtonPress, onSubmit}: BankInfoProps) { prevScreen, moveTo, goToTheLastStep, - resetScreenIndex, } = useSubStep({bodyContent, startFrom: 0, onFinished: submit}); - // Temporary solution to get the fields for the corpay bank account fields - useEffect(() => { - const response = BankAccounts.getCorpayBankAccountFields(country, currency); - setCorpayFields((response?.formFields as CorpayFormField[]) ?? []); - }, [country, currency]); - const handleBackButtonPress = () => { if (isEditing) { goToTheLastStep(); @@ -65,27 +54,11 @@ function BankInfo({onBackButtonPress, onSubmit}: BankInfoProps) { if (screenIndex === 0) { onBackButtonPress(); - } else if (currency === CONST.CURRENCY.AUD) { - prevScreen(); } else { - resetScreenIndex(); + prevScreen(); } }; - const handleNextScreen = useCallback(() => { - if (screenIndex === 2) { - nextScreen(); - return; - } - - if (currency !== CONST.CURRENCY.AUD) { - goToTheLastStep(); - return; - } - - nextScreen(); - }, [currency, goToTheLastStep, nextScreen, screenIndex]); - return ( ); diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/AccountHolderDetails.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/AccountHolderDetails.tsx new file mode 100644 index 000000000000..c0187b9ce7c7 --- /dev/null +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/AccountHolderDetails.tsx @@ -0,0 +1,134 @@ +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import PushRowWithModal from '@components/PushRowWithModal'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {BankInfoSubStepProps} from '@pages/ReimbursementAccount/NonUSD/BankInfo/types'; +import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReimbursementAccountForm} from '@src/types/form/ReimbursementAccountForm'; +import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; + +const {ACCOUNT_HOLDER_COUNTRY} = INPUT_IDS.ADDITIONAL_DATA; + +function AccountHolderDetails({onNext, isEditing, corpayFields}: BankInfoSubStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const accountHolderDetailsFields = useMemo(() => { + return corpayFields?.filter((field) => field.id.includes(CONST.NON_USD_BANK_ACCOUNT.BANK_INFO_STEP_ACCOUNT_HOLDER_KEY_PREFIX)); + }, [corpayFields]); + const fieldIds = accountHolderDetailsFields?.map((field) => field.id); + + const subStepKeys = accountHolderDetailsFields?.reduce((acc, field) => { + acc[field.id as keyof ReimbursementAccountForm] = field.id as keyof ReimbursementAccountForm; + return acc; + }, {} as Record); + + const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); + const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); + const defaultValues = useMemo(() => getSubstepValues(subStepKeys ?? {}, reimbursementAccountDraft, reimbursementAccount), [subStepKeys, reimbursementAccount, reimbursementAccountDraft]); + + const handleSubmit = useReimbursementAccountStepFormSubmit({ + fieldIds: fieldIds as Array>, + onNext, + shouldSaveDraft: isEditing, + }); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + + accountHolderDetailsFields?.forEach((field) => { + const fieldID = field.id as keyof FormOnyxValues; + + if (field.isRequired && !values[fieldID]) { + errors[fieldID] = translate('common.error.fieldRequired'); + } + + field.validationRules.forEach((rule) => { + if (!rule.regEx) { + return; + } + + if (new RegExp(rule.regEx).test(values[fieldID] ? String(values[fieldID]) : '')) { + return; + } + + errors[fieldID] = rule.errorMessage; + }); + }); + + return errors; + }, + [accountHolderDetailsFields, translate], + ); + + const inputs = useMemo(() => { + return accountHolderDetailsFields?.map((field) => { + if (field.id === ACCOUNT_HOLDER_COUNTRY) { + return ( + + + + ); + } + + return ( + + + + ); + }); + }, [accountHolderDetailsFields, styles.flex2, styles.mb6, styles.mhn5, defaultValues, isEditing, translate]); + + return ( + + + {translate('bankInfoStep.whatAreYour')} + {inputs} + + + ); +} + +AccountHolderDetails.displayName = 'AccountHolderDetails'; + +export default AccountHolderDetails; diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/BankAccountDetails.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/BankAccountDetails.tsx index b3482a516c1f..d9bb9fe19671 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/BankAccountDetails.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/BankAccountDetails.tsx @@ -16,13 +16,17 @@ function BankAccountDetails({onNext, isEditing, corpayFields}: BankInfoSubStepPr const {translate} = useLocalize(); const styles = useThemeStyles(); - const fieldIds = corpayFields.map((field) => field.id); + const bankAccountDetailsFields = useMemo(() => { + return corpayFields?.filter((field) => !field.id.includes(CONST.NON_USD_BANK_ACCOUNT.BANK_INFO_STEP_ACCOUNT_HOLDER_KEY_PREFIX)); + }, [corpayFields]); + + const fieldIds = bankAccountDetailsFields?.map((field) => field.id); const validate = useCallback( (values: FormOnyxValues): FormInputErrors => { const errors: FormInputErrors = {}; - corpayFields.forEach((field) => { + corpayFields?.forEach((field) => { const fieldID = field.id as keyof FormOnyxValues; if (field.isRequired && !values[fieldID]) { @@ -54,7 +58,7 @@ function BankAccountDetails({onNext, isEditing, corpayFields}: BankInfoSubStepPr }); const inputs = useMemo(() => { - return corpayFields.map((field) => { + return bankAccountDetailsFields?.map((field) => { return ( ); }); - }, [corpayFields, styles.flex2, styles.mb6, isEditing]); + }, [bankAccountDetailsFields, styles.flex2, styles.mb6, isEditing]); return ( { const keys: Record = {}; - corpayFields.forEach((field) => { - keys[field.id] = field.id; + corpayFields?.forEach((field) => { + keys[field.id] = field.id as keyof ReimbursementAccountForm; }); return keys; }, [corpayFields]); @@ -32,11 +32,11 @@ function Confirmation({onNext, onMove, corpayFields}: BankInfoSubStepProps) { const items = useMemo( () => ( <> - {corpayFields.map((field) => { + {corpayFields?.map((field) => { return ( { onMove(0); diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/types.ts b/src/pages/ReimbursementAccount/NonUSD/BankInfo/types.ts index 17943b29e3d3..4946b95ee496 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/types.ts +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/types.ts @@ -1,17 +1,6 @@ import type {SubStepProps} from '@hooks/useSubStep/types'; -import type {ReimbursementAccountForm} from '@src/types/form'; +import type {CorpayFormField} from '@src/types/onyx/CorpayFields'; -type CorpayFormField = { - id: keyof ReimbursementAccountForm; - isRequired: boolean; - errorMessage: string; - label: string; - regEx?: string; - validationRules: Array<{errorMessage: string; regEx: string}>; - defaultValue?: string; - detailedRule?: Array<{isRequired: boolean; value: Array<{errorMessage: string; regEx: string; ruleDescription: string}>}>; -}; - -type BankInfoSubStepProps = SubStepProps & {corpayFields: CorpayFormField[]}; +type BankInfoSubStepProps = SubStepProps & {corpayFields?: CorpayFormField[]}; export type {BankInfoSubStepProps, CorpayFormField}; diff --git a/src/types/onyx/CorpayFields.ts b/src/types/onyx/CorpayFields.ts new file mode 100644 index 000000000000..da4c93b66834 --- /dev/null +++ b/src/types/onyx/CorpayFields.ts @@ -0,0 +1,42 @@ +/** + * Represents a form field with validation rules. + */ +type CorpayFormField = { + /** Error message for the form field */ + errorMessage: string; + /** Unique identifier for the form field */ + id: string; + /** Indicates if the field is required */ + isRequired: boolean; + /** Indicates if the field is required in the value set */ + isRequiredInValueSet: boolean; + /** Label for the form field */ + label: string; + /** Regular expression for the form field */ + regEx: string; + /** Validation rules for the form field */ + validationRules: Array<{ + /** Error message for the validation rule */ + errorMessage: string; + /** Regular expression for the validation rule */ + regEx: string; + }>; +}; + +/** CorpayFormFields */ +type CorpayFormFields = { + /** Country of the bank */ + bankCountry: string; + /** Currency of the bank */ + bankCurrency: string; + /** Classification of the bank */ + classification: string; + /** Destination country of the bank */ + destinationCountry: string; + /** Form fields for the Corpay form */ + formFields: CorpayFormField[]; +}; + +export default CorpayFormFields; + +export type {CorpayFormField}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index cec5243990a6..21fed723a07e 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -16,6 +16,7 @@ import type CardFeeds from './CardFeeds'; import type {AddNewCompanyCardFeed, CompanyCardFeed} from './CardFeeds'; import type CardOnWaitlist from './CardOnWaitlist'; import type {CapturedLogs, Log} from './Console'; +import type CorpayFields from './CorpayFields'; import type Credentials from './Credentials'; import type Currency from './Currency'; import type {CurrencyList} from './Currency'; @@ -124,6 +125,7 @@ export type { CardList, CardOnWaitlist, Credentials, + CorpayFields, Currency, CurrencyList, CustomStatusDraft, From 57a8eac193a21a56b644126b2ce476f685fe9c6a Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Wed, 20 Nov 2024 20:42:41 +0700 Subject: [PATCH 050/161] fix clear cached data after account switch --- src/libs/actions/QueuedOnyxUpdates.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index bc19ff12aea1..b9d2a20bdd2c 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -1,9 +1,18 @@ -import type {OnyxUpdate} from 'react-native-onyx'; +import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; // In this file we manage a queue of Onyx updates while the SequentialQueue is processing. There are functions to get the updates and clear the queue after saving the updates in Onyx. let queuedOnyxUpdates: OnyxUpdate[] = []; +let currentAccountID: number | undefined; + +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (session) => { + currentAccountID = session?.accountID; + }, +}); /** * @param updates Onyx updates to queue for later @@ -14,6 +23,20 @@ function queueOnyxUpdates(updates: OnyxUpdate[]): Promise { } function flushQueue(): Promise { + if (!currentAccountID) { + const preservedKeys: OnyxKey[] = [ + ONYXKEYS.NVP_TRY_FOCUS_MODE, + ONYXKEYS.PREFERRED_THEME, + ONYXKEYS.NVP_PREFERRED_LOCALE, + ONYXKEYS.SESSION, + ONYXKEYS.IS_LOADING_APP, + ONYXKEYS.CREDENTIALS, + ONYXKEYS.IS_SIDEBAR_LOADED, + ]; + + queuedOnyxUpdates = queuedOnyxUpdates.filter((update) => preservedKeys.includes(update.key as OnyxKey)); + } + return Onyx.update(queuedOnyxUpdates).then(() => { queuedOnyxUpdates = []; }); From d4b216546a4fa66cf5b8056412dadadcc14ab179 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Wed, 20 Nov 2024 15:08:20 +0100 Subject: [PATCH 051/161] fix: variable value --- .../NonUSD/BankInfo/substeps/AccountHolderDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/AccountHolderDetails.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/AccountHolderDetails.tsx index c0187b9ce7c7..acb69d3312bb 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/AccountHolderDetails.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/AccountHolderDetails.tsx @@ -17,7 +17,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form/ReimbursementAccountForm'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; -const {ACCOUNT_HOLDER_COUNTRY} = INPUT_IDS.ADDITIONAL_DATA; +const {ACCOUNT_HOLDER_COUNTRY} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; function AccountHolderDetails({onNext, isEditing, corpayFields}: BankInfoSubStepProps) { const {translate} = useLocalize(); From cd04c395d16a28ed90cdf56ad0e978480333b99a Mon Sep 17 00:00:00 2001 From: Huu Le <20178761+huult@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:22:56 +0700 Subject: [PATCH 052/161] Skip executing filter queue update for Onyx in test environment --- src/CONFIG.ts | 1 + src/libs/actions/QueuedOnyxUpdates.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CONFIG.ts b/src/CONFIG.ts index e5e9a9d1540a..72f98c0ee106 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -104,4 +104,5 @@ export default { // to read more about StrictMode see: contributingGuides/STRICT_MODE.md USE_REACT_STRICT_MODE_IN_DEV: false, ELECTRON_DISABLE_SECURITY_WARNINGS: 'true', + IS_TEST_ENV: process.env.NODE_ENV === 'test', } as const; diff --git a/src/libs/actions/QueuedOnyxUpdates.ts b/src/libs/actions/QueuedOnyxUpdates.ts index b9d2a20bdd2c..74c5cb6b12f2 100644 --- a/src/libs/actions/QueuedOnyxUpdates.ts +++ b/src/libs/actions/QueuedOnyxUpdates.ts @@ -1,5 +1,6 @@ import type {OnyxKey, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import CONFIG from '@src/CONFIG'; import ONYXKEYS from '@src/ONYXKEYS'; // In this file we manage a queue of Onyx updates while the SequentialQueue is processing. There are functions to get the updates and clear the queue after saving the updates in Onyx. @@ -23,7 +24,7 @@ function queueOnyxUpdates(updates: OnyxUpdate[]): Promise { } function flushQueue(): Promise { - if (!currentAccountID) { + if (!currentAccountID && !CONFIG.IS_TEST_ENV) { const preservedKeys: OnyxKey[] = [ ONYXKEYS.NVP_TRY_FOCUS_MODE, ONYXKEYS.PREFERRED_THEME, From f375a4f2b2e6491a4884c769ffa75e418379cdc9 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Fri, 22 Nov 2024 13:53:30 +0100 Subject: [PATCH 053/161] fix: web project crashes --- .../executeOnUIRuntimeSync/index.native.ts | 3 +++ .../useWorkletStateMachine/executeOnUIRuntimeSync/index.ts | 3 +++ .../index.ts} | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useWorkletStateMachine/executeOnUIRuntimeSync/index.native.ts create mode 100644 src/hooks/useWorkletStateMachine/executeOnUIRuntimeSync/index.ts rename src/hooks/{useWorkletStateMachine.ts => useWorkletStateMachine/index.ts} (97%) diff --git a/src/hooks/useWorkletStateMachine/executeOnUIRuntimeSync/index.native.ts b/src/hooks/useWorkletStateMachine/executeOnUIRuntimeSync/index.native.ts new file mode 100644 index 000000000000..eab78097aa05 --- /dev/null +++ b/src/hooks/useWorkletStateMachine/executeOnUIRuntimeSync/index.native.ts @@ -0,0 +1,3 @@ +import {executeOnUIRuntimeSync} from 'react-native-reanimated'; + +export default executeOnUIRuntimeSync; diff --git a/src/hooks/useWorkletStateMachine/executeOnUIRuntimeSync/index.ts b/src/hooks/useWorkletStateMachine/executeOnUIRuntimeSync/index.ts new file mode 100644 index 000000000000..3bc8059d8762 --- /dev/null +++ b/src/hooks/useWorkletStateMachine/executeOnUIRuntimeSync/index.ts @@ -0,0 +1,3 @@ +import {runOnUI} from 'react-native-reanimated'; + +export default runOnUI; diff --git a/src/hooks/useWorkletStateMachine.ts b/src/hooks/useWorkletStateMachine/index.ts similarity index 97% rename from src/hooks/useWorkletStateMachine.ts rename to src/hooks/useWorkletStateMachine/index.ts index 105814c094eb..dcedf002fc15 100644 --- a/src/hooks/useWorkletStateMachine.ts +++ b/src/hooks/useWorkletStateMachine/index.ts @@ -1,6 +1,7 @@ import {useCallback} from 'react'; -import {executeOnUIRuntimeSync, runOnJS, runOnUI, useSharedValue} from 'react-native-reanimated'; +import {runOnJS, runOnUI, useSharedValue} from 'react-native-reanimated'; import Log from '@libs/Log'; +import executeOnUIRuntimeSync from './executeOnUIRuntimeSync'; // When you need to debug state machine change this to true const DEBUG_MODE = false; From 4e7d9316e9a2626514043c5f373b54082db8a863 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Fri, 22 Nov 2024 14:03:50 +0100 Subject: [PATCH 054/161] fix: prettier --- src/pages/home/report/ReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 1e0f4140a6c8..1f1d5e0fdf4f 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -370,7 +370,7 @@ function ReportActionItem({ } handleShowContextMenu(() => { - setIsContextMenuActive(true); + setIsContextMenuActive(true); const selection = SelectionScraper.getCurrentSelection(); ReportActionContextMenu.showContextMenu( CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, From b48a247a6f20a0eec11c4e73bd19af604c9ec867 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Fri, 22 Nov 2024 14:58:05 +0100 Subject: [PATCH 055/161] fix: long press of a video attachment does not push up the video message --- .../VideoPlayerPreview/VideoPlayerThumbnail.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx b/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx index 832b5eef45f0..e1c1a000d9bd 100644 --- a/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx +++ b/src/components/VideoPlayerPreview/VideoPlayerThumbnail.tsx @@ -45,7 +45,7 @@ function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel, isDele )} {!isDeleted ? ( - {({anchor, report, reportNameValuePairs, action, checkIfContextMenuActive, isDisabled}) => ( + {({anchor, report, reportNameValuePairs, action, checkIfContextMenuActive, isDisabled, onShowContextMenu}) => ( { + showContextMenuForReport( + event, + anchor, + report?.reportID ?? '-1', + action, + checkIfContextMenuActive, + ReportUtils.isArchivedRoom(report, reportNameValuePairs), + ); + }); }} shouldUseHapticsOnLongPress > From 54e779f6d6722d1f7579bf1a8161280c6480fde2 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Tue, 26 Nov 2024 10:12:22 +0100 Subject: [PATCH 056/161] feat: Add Corpay bank account creation parameters and update related components --- .../BankAccountCreateCorpayParams.ts | 8 ++++++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 ++ src/libs/actions/BankAccounts.ts | 14 +++++++++- .../NonUSD/BankInfo/BankInfo.tsx | 13 ++++++++- .../BankInfo/substeps/BankAccountDetails.tsx | 2 +- .../NonUSD/BankInfo/substeps/Confirmation.tsx | 10 +++++-- .../NonUSD/BankInfo/types.ts | 2 +- .../NonUSD/BusinessInfo/substeps/Address.tsx | 2 +- .../NonUSD/Country/substeps/Confirmation.tsx | 27 ++++++++++--------- src/types/form/ReimbursementAccountForm.ts | 12 +++++++-- src/types/onyx/CorpayFields.ts | 2 ++ src/types/onyx/ReimbursementAccount.ts | 4 ++- 13 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 src/libs/API/parameters/BankAccountCreateCorpayParams.ts diff --git a/src/libs/API/parameters/BankAccountCreateCorpayParams.ts b/src/libs/API/parameters/BankAccountCreateCorpayParams.ts new file mode 100644 index 000000000000..3c617d326009 --- /dev/null +++ b/src/libs/API/parameters/BankAccountCreateCorpayParams.ts @@ -0,0 +1,8 @@ +type BankAccountCreateCorpayParams = { + type: number; + isSavings: boolean; + isWithdrawal: boolean; + inputs: string; +}; + +export default BankAccountCreateCorpayParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index feb73af644bc..1f3a3500a229 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -8,6 +8,7 @@ export type {default as RestartBankAccountSetupParams} from './RestartBankAccoun export type {default as AddSchoolPrincipalParams} from './AddSchoolPrincipalParams'; export type {default as AuthenticatePusherParams} from './AuthenticatePusherParams'; export type {default as BankAccountHandlePlaidErrorParams} from './BankAccountHandlePlaidErrorParams'; +export type {default as BankAccountCreateCorpayParams} from './BankAccountCreateCorpayParams'; export type {default as BeginAppleSignInParams} from './BeginAppleSignInParams'; export type {default as BeginGoogleSignInParams} from './BeginGoogleSignInParams'; export type {default as BeginSignInParams} from './BeginSignInParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 55da017ff5e2..3b4af17b492f 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -437,6 +437,7 @@ const WRITE_COMMANDS = { SELF_TOUR_VIEWED: 'SelfTourViewed', UPDATE_INVOICE_COMPANY_NAME: 'UpdateInvoiceCompanyName', UPDATE_INVOICE_COMPANY_WEBSITE: 'UpdateInvoiceCompanyWebsite', + BANK_ACCOUNT_CREATE_CORPAY: 'BankAccount_CreateCorpay', } as const; type WriteCommand = ValueOf; @@ -765,6 +766,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.APPROVE_MONEY_REQUEST_ON_SEARCH]: Parameters.ApproveMoneyRequestOnSearchParams; [WRITE_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH]: Parameters.PayMoneyRequestOnSearchParams; [WRITE_COMMANDS.UNHOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.UnholdMoneyRequestOnSearchParams; + [WRITE_COMMANDS.BANK_ACCOUNT_CREATE_CORPAY]: Parameters.BankAccountCreateCorpayParams; [WRITE_COMMANDS.REQUEST_REFUND]: null; [WRITE_COMMANDS.CONNECT_POLICY_TO_SAGE_INTACCT]: Parameters.ConnectPolicyToSageIntacctParams; diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 4f149a026ddf..067511e03fc2 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -20,7 +20,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import type {PersonalBankAccountForm} from '@src/types/form'; -import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, RequestorStepProps} from '@src/types/form/ReimbursementAccountForm'; +import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, ReimbursementAccountForm, RequestorStepProps} from '@src/types/form/ReimbursementAccountForm'; import type PlaidBankAccount from '@src/types/onyx/PlaidBankAccount'; import type {BankAccountStep, ReimbursementAccountStep, ReimbursementAccountSubStep} from '@src/types/onyx/ReimbursementAccount'; import type {OnyxData} from '@src/types/onyx/Request'; @@ -354,6 +354,17 @@ function getCorpayBankAccountFields(country: string, currency: string) { return API.read(READ_COMMANDS.GET_CORPAY_BANK_ACCOUNT_FIELDS, parameters); } +function createCorpayBankAccount(fields: ReimbursementAccountForm) { + const parameters = { + type: 1, + isSavings: false, + isWithdrawal: true, + inputs: JSON.stringify(fields), + }; + + return API.write(WRITE_COMMANDS.BANK_ACCOUNT_CREATE_CORPAY, parameters); +} + function clearReimbursementAccount() { Onyx.set(ONYXKEYS.REIMBURSEMENT_ACCOUNT, null); } @@ -566,6 +577,7 @@ export { openPlaidView, connectBankAccountManually, connectBankAccountWithPlaid, + createCorpayBankAccount, deletePaymentBankAccount, handlePlaidError, setPersonalBankAccountContinueKYCOnSuccess, diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx index 66cd84593a71..cf83a2365669 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx @@ -1,17 +1,21 @@ import type {ComponentType} from 'react'; -import React from 'react'; +import React, {useEffect} from 'react'; import {useOnyx} from 'react-native-onyx'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; +import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; import AccountHolderDetails from './substeps/AccountHolderDetails'; import BankAccountDetails from './substeps/BankAccountDetails'; import Confirmation from './substeps/Confirmation'; import UploadStatement from './substeps/UploadStatement'; import type {BankInfoSubStepProps} from './types'; +const {DESTINATION_COUNTRY} = INPUT_IDS.ADDITIONAL_DATA; + type BankInfoProps = { /** Handles back button press */ onBackButtonPress: () => void; @@ -24,15 +28,21 @@ function BankInfo({onBackButtonPress, onSubmit}: BankInfoProps) { const {translate} = useLocalize(); const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); + const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT); const [corpayFields] = useOnyx(ONYXKEYS.CORPAY_FIELDS); const policyID = reimbursementAccount?.achData?.policyID ?? '-1'; const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); const currency = policy?.outputCurrency ?? ''; + const country = reimbursementAccountDraft?.[DESTINATION_COUNTRY] ?? ''; const submit = () => { onSubmit(); }; + useEffect(() => { + BankAccounts.getCorpayBankAccountFields(country, currency); + }, [country, currency]); + const bodyContent: Array> = currency !== CONST.CURRENCY.AUD ? [BankAccountDetails, AccountHolderDetails, Confirmation] : [BankAccountDetails, AccountHolderDetails, UploadStatement, Confirmation]; @@ -72,6 +82,7 @@ function BankInfo({onBackButtonPress, onSubmit}: BankInfoProps) { onNext={nextScreen} onMove={moveTo} corpayFields={corpayFields?.formFields} + preferredMethod={corpayFields?.preferredMethod} /> ); diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/BankAccountDetails.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/BankAccountDetails.tsx index d9bb9fe19671..5e1356430440 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/BankAccountDetails.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/BankAccountDetails.tsx @@ -34,7 +34,7 @@ function BankAccountDetails({onNext, isEditing, corpayFields}: BankInfoSubStepPr } field.validationRules.forEach((rule) => { - if (rule.regEx) { + if (!rule.regEx) { return; } diff --git a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/Confirmation.tsx b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/Confirmation.tsx index c10c98b77418..2d22b52d28b6 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/Confirmation.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BankInfo/substeps/Confirmation.tsx @@ -10,11 +10,12 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import type {BankInfoSubStepProps} from '@pages/ReimbursementAccount/NonUSD/BankInfo/types'; import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; +import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReimbursementAccountForm} from '@src/types/form/ReimbursementAccountForm'; import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; -function Confirmation({onNext, onMove, corpayFields}: BankInfoSubStepProps) { +function Confirmation({onNext, onMove, corpayFields, preferredMethod}: BankInfoSubStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -58,6 +59,11 @@ function Confirmation({onNext, onMove, corpayFields}: BankInfoSubStepProps) { [corpayFields, onMove, reimbursementAccountDraft, translate, values], ); + const handleNext = () => { + BankAccounts.createCorpayBankAccount({...reimbursementAccountDraft, preferredMethod} as ReimbursementAccountForm); + onNext(); + }; + return ( {({safeAreaPaddingBottomStyle}) => ( @@ -72,7 +78,7 @@ function Confirmation({onNext, onMove, corpayFields}: BankInfoSubStepProps) {