From e8f948f68656e68853e5522a8b4df732e05e6e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 19 Oct 2023 14:24:41 +0200 Subject: [PATCH 001/763] install native-stack --- package-lock.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 46 insertions(+) diff --git a/package-lock.json b/package-lock.json index 95c25f602e30..75d3e9fe0ed0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "@react-native-picker/picker": "^2.4.3", "@react-navigation/material-top-tabs": "^6.6.3", "@react-navigation/native": "6.1.6", + "@react-navigation/native-stack": "^6.9.14", "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", @@ -8961,6 +8962,33 @@ "react-native": "*" } }, + "node_modules/@react-navigation/native-stack": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-6.9.14.tgz", + "integrity": "sha512-7RiZkvMFN6f0kmANc63B/0m9ttQ2JpDIPWQwPU93FP698s19KTOyu7uxgl7Oi3bvsqHFO5JfiR7B+4h8lh9dxw==", + "dependencies": { + "@react-navigation/elements": "^1.3.19", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^6.0.0", + "react": "*", + "react-native": "*", + "react-native-safe-area-context": ">= 3.0.0", + "react-native-screens": ">= 3.0.0" + } + }, + "node_modules/@react-navigation/native-stack/node_modules/@react-navigation/elements": { + "version": "1.3.19", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.19.tgz", + "integrity": "sha512-7hLvSYKPuDS070pza5gd43WDX7QgfuEmuTWNbCJhKdWlLudYmq3qzxGCBwCfO2dEI6+p8tla5wruaWiGKAbTYw==", + "peerDependencies": { + "@react-navigation/native": "^6.0.0", + "react": "*", + "react-native": "*", + "react-native-safe-area-context": ">= 3.0.0" + } + }, "node_modules/@react-navigation/routers": { "version": "6.1.8", "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.8.tgz", @@ -59320,6 +59348,23 @@ "nanoid": "^3.1.23" } }, + "@react-navigation/native-stack": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-6.9.14.tgz", + "integrity": "sha512-7RiZkvMFN6f0kmANc63B/0m9ttQ2JpDIPWQwPU93FP698s19KTOyu7uxgl7Oi3bvsqHFO5JfiR7B+4h8lh9dxw==", + "requires": { + "@react-navigation/elements": "^1.3.19", + "warn-once": "^0.1.0" + }, + "dependencies": { + "@react-navigation/elements": { + "version": "1.3.19", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.19.tgz", + "integrity": "sha512-7hLvSYKPuDS070pza5gd43WDX7QgfuEmuTWNbCJhKdWlLudYmq3qzxGCBwCfO2dEI6+p8tla5wruaWiGKAbTYw==", + "requires": {} + } + } + }, "@react-navigation/routers": { "version": "6.1.8", "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.8.tgz", diff --git a/package.json b/package.json index 1db859827c41..665951328bd1 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@react-native-picker/picker": "^2.4.3", "@react-navigation/material-top-tabs": "^6.6.3", "@react-navigation/native": "6.1.6", + "@react-navigation/native-stack": "^6.9.14", "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", From b180872b639ceb3e4aa399645c9c85ec87b0b0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 19 Oct 2023 15:14:37 +0200 Subject: [PATCH 002/763] create PlatformStackNavigator with different impls for web & native --- src/libs/Navigation/AppNavigator/ModalStackNavigators.js | 7 +++++-- .../AppNavigator/Navigators/CentralPaneNavigator.js | 4 ++-- src/libs/Navigation/AppNavigator/Navigators/Overlay.js | 1 + .../AppNavigator/Navigators/RightModalNavigator.js | 4 ++-- src/libs/Navigation/AppNavigator/PublicScreens.js | 4 ++-- .../AppNavigator/createCustomStackNavigator/index.js | 4 ++-- src/libs/Navigation/PlatformStackNavigator/index.native.ts | 7 +++++++ src/libs/Navigation/PlatformStackNavigator/index.ts | 7 +++++++ 8 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 src/libs/Navigation/PlatformStackNavigator/index.native.ts create mode 100644 src/libs/Navigation/PlatformStackNavigator/index.ts diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index cfc8f815e4fe..2e5950e1b094 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -1,9 +1,12 @@ import _ from 'underscore'; import React from 'react'; -import {createStackNavigator, CardStyleInterpolators} from '@react-navigation/stack'; +import {CardStyleInterpolators} from '@react-navigation/stack'; +import * as PlatformStackNavigator from '../PlatformStackNavigator'; + import styles from '../../../styles/styles'; import SCREENS from '../../../SCREENS'; +// TODO: migrate options const defaultSubRouteOptions = { cardStyle: styles.navigationScreenCardStyle, headerShown: false, @@ -17,7 +20,7 @@ const defaultSubRouteOptions = { * @returns {Function} */ function createModalStackNavigator(screens) { - const ModalStackNavigator = createStackNavigator(); + const ModalStackNavigator = PlatformStackNavigator.createPlatformStackNavigator(); return () => ( {_.map(screens, (getComponent, name) => ( diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js index 64eadcbe06c3..b5fa76a48b90 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js @@ -1,12 +1,12 @@ import React from 'react'; -import {createStackNavigator} from '@react-navigation/stack'; +import * as PlatformStackNavigator from '../../PlatformStackNavigator'; import SCREENS from '../../../../SCREENS'; import ReportScreenWrapper from '../ReportScreenWrapper'; import getCurrentUrl from '../../currentUrl'; import styles from '../../../../styles/styles'; import FreezeWrapper from '../../FreezeWrapper'; -const Stack = createStackNavigator(); +const Stack = PlatformStackNavigator.createPlatformStackNavigator(); const url = getCurrentUrl(); const openOnAdminRoom = url ? new URL(url).searchParams.get('openOnAdminRoom') : undefined; diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay.js b/src/libs/Navigation/AppNavigator/Navigators/Overlay.js index 1b2faff8c5e3..ed3c27aed673 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/Overlay.js +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay.js @@ -1,5 +1,6 @@ import React from 'react'; import {Animated, View} from 'react-native'; +// TODO: migrate import {useCardAnimation} from '@react-navigation/stack'; import PropTypes from 'prop-types'; diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js index 5f24ec159828..822a1c6ead55 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js @@ -1,6 +1,6 @@ import React from 'react'; import {View} from 'react-native'; -import {createStackNavigator} from '@react-navigation/stack'; +import * as PlatformStackNavigator from '../../PlatformStackNavigator'; import * as ModalStackNavigators from '../ModalStackNavigators'; import RHPScreenOptions from '../RHPScreenOptions'; @@ -10,7 +10,7 @@ import styles from '../../../../styles/styles'; import Overlay from './Overlay'; import NoDropZone from '../../../../components/DragAndDrop/NoDropZone'; -const Stack = createStackNavigator(); +const Stack = PlatformStackNavigator.createPlatformStackNavigator(); const propTypes = { ...withNavigationPropTypes, diff --git a/src/libs/Navigation/AppNavigator/PublicScreens.js b/src/libs/Navigation/AppNavigator/PublicScreens.js index 7b0afb787278..6a2ad51cab6b 100644 --- a/src/libs/Navigation/AppNavigator/PublicScreens.js +++ b/src/libs/Navigation/AppNavigator/PublicScreens.js @@ -1,5 +1,5 @@ import React from 'react'; -import {createStackNavigator} from '@react-navigation/stack'; +import * as PlatformStackNavigator from '../PlatformStackNavigator'; import SignInPage from '../../../pages/signin/SignInPage'; import ValidateLoginPage from '../../../pages/ValidateLoginPage'; import LogInWithShortLivedAuthTokenPage from '../../../pages/LogInWithShortLivedAuthTokenPage'; @@ -10,7 +10,7 @@ import AppleSignInDesktopPage from '../../../pages/signin/AppleSignInDesktopPage import GoogleSignInDesktopPage from '../../../pages/signin/GoogleSignInDesktopPage'; import SAMLSignInPage from '../../../pages/signin/SAMLSignInPage'; -const RootStack = createStackNavigator(); +const RootStack = PlatformStackNavigator.createPlatformStackNavigator(); function PublicScreens() { return ( diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js index 58be3d2af3da..5b9679426a4f 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js @@ -1,7 +1,7 @@ import React, {useRef} from 'react'; import PropTypes from 'prop-types'; import {useNavigationBuilder, createNavigatorFactory} from '@react-navigation/native'; -import {StackView} from '@react-navigation/stack'; +import * as PlatformStackNavigator from '../../PlatformStackNavigator'; import CustomRouter from './CustomRouter'; import useWindowDimensions from '../../../../hooks/useWindowDimensions'; @@ -42,7 +42,7 @@ function ResponsiveStackNavigator(props) { return ( - Date: Thu, 19 Oct 2023 15:22:14 +0200 Subject: [PATCH 003/763] create separate functions --- src/libs/Navigation/AppNavigator/ModalStackNavigators.js | 4 ++-- .../AppNavigator/Navigators/CentralPaneNavigator.js | 4 ++-- .../AppNavigator/Navigators/RightModalNavigator.js | 4 ++-- src/libs/Navigation/AppNavigator/PublicScreens.js | 4 ++-- .../AppNavigator/createCustomStackNavigator/index.js | 4 ++-- .../Navigation/PlatformStackNavigation/StackView.native.ts | 3 +++ src/libs/Navigation/PlatformStackNavigation/StackView.ts | 3 +++ .../createPlatformStackNavigator.native.ts | 7 +++++++ .../createPlatformStackNavigator.ts | 7 +++++++ src/libs/Navigation/PlatformStackNavigator/index.native.ts | 7 ------- src/libs/Navigation/PlatformStackNavigator/index.ts | 7 ------- 11 files changed, 30 insertions(+), 24 deletions(-) create mode 100644 src/libs/Navigation/PlatformStackNavigation/StackView.native.ts create mode 100644 src/libs/Navigation/PlatformStackNavigation/StackView.ts create mode 100644 src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator.native.ts create mode 100644 src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator.ts delete mode 100644 src/libs/Navigation/PlatformStackNavigator/index.native.ts delete mode 100644 src/libs/Navigation/PlatformStackNavigator/index.ts diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 2e5950e1b094..985aacc9da96 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -1,7 +1,7 @@ import _ from 'underscore'; import React from 'react'; import {CardStyleInterpolators} from '@react-navigation/stack'; -import * as PlatformStackNavigator from '../PlatformStackNavigator'; +import createPlatformStackNavigator from '../PlatformStackNavigation/createPlatformStackNavigator'; import styles from '../../../styles/styles'; import SCREENS from '../../../SCREENS'; @@ -20,7 +20,7 @@ const defaultSubRouteOptions = { * @returns {Function} */ function createModalStackNavigator(screens) { - const ModalStackNavigator = PlatformStackNavigator.createPlatformStackNavigator(); + const ModalStackNavigator = createPlatformStackNavigator(); return () => ( {_.map(screens, (getComponent, name) => ( diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js index b5fa76a48b90..754a25694785 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator.js @@ -1,12 +1,12 @@ import React from 'react'; -import * as PlatformStackNavigator from '../../PlatformStackNavigator'; +import createPlatformStackNavigator from '../../PlatformStackNavigation/createPlatformStackNavigator'; import SCREENS from '../../../../SCREENS'; import ReportScreenWrapper from '../ReportScreenWrapper'; import getCurrentUrl from '../../currentUrl'; import styles from '../../../../styles/styles'; import FreezeWrapper from '../../FreezeWrapper'; -const Stack = PlatformStackNavigator.createPlatformStackNavigator(); +const Stack = createPlatformStackNavigator(); const url = getCurrentUrl(); const openOnAdminRoom = url ? new URL(url).searchParams.get('openOnAdminRoom') : undefined; diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js index 822a1c6ead55..2086490c597f 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js @@ -1,6 +1,6 @@ import React from 'react'; import {View} from 'react-native'; -import * as PlatformStackNavigator from '../../PlatformStackNavigator'; +import createPlatformStackNavigator from '../../PlatformStackNavigation/createPlatformStackNavigator'; import * as ModalStackNavigators from '../ModalStackNavigators'; import RHPScreenOptions from '../RHPScreenOptions'; @@ -10,7 +10,7 @@ import styles from '../../../../styles/styles'; import Overlay from './Overlay'; import NoDropZone from '../../../../components/DragAndDrop/NoDropZone'; -const Stack = PlatformStackNavigator.createPlatformStackNavigator(); +const Stack = createPlatformStackNavigator(); const propTypes = { ...withNavigationPropTypes, diff --git a/src/libs/Navigation/AppNavigator/PublicScreens.js b/src/libs/Navigation/AppNavigator/PublicScreens.js index 6a2ad51cab6b..720784f97ec1 100644 --- a/src/libs/Navigation/AppNavigator/PublicScreens.js +++ b/src/libs/Navigation/AppNavigator/PublicScreens.js @@ -1,5 +1,5 @@ import React from 'react'; -import * as PlatformStackNavigator from '../PlatformStackNavigator'; +import createPlatformStackNavigator from '../PlatformStackNavigation/createPlatformStackNavigator'; import SignInPage from '../../../pages/signin/SignInPage'; import ValidateLoginPage from '../../../pages/ValidateLoginPage'; import LogInWithShortLivedAuthTokenPage from '../../../pages/LogInWithShortLivedAuthTokenPage'; @@ -10,7 +10,7 @@ import AppleSignInDesktopPage from '../../../pages/signin/AppleSignInDesktopPage import GoogleSignInDesktopPage from '../../../pages/signin/GoogleSignInDesktopPage'; import SAMLSignInPage from '../../../pages/signin/SAMLSignInPage'; -const RootStack = PlatformStackNavigator.createPlatformStackNavigator(); +const RootStack = createPlatformStackNavigator(); function PublicScreens() { return ( diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js index 5b9679426a4f..60894018ffd3 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.js @@ -1,7 +1,7 @@ import React, {useRef} from 'react'; import PropTypes from 'prop-types'; import {useNavigationBuilder, createNavigatorFactory} from '@react-navigation/native'; -import * as PlatformStackNavigator from '../../PlatformStackNavigator'; +import StackView from '../../PlatformStackNavigation/StackView'; import CustomRouter from './CustomRouter'; import useWindowDimensions from '../../../../hooks/useWindowDimensions'; @@ -42,7 +42,7 @@ function ResponsiveStackNavigator(props) { return ( - Date: Thu, 19 Oct 2023 17:06:52 +0200 Subject: [PATCH 004/763] fix right modal options --- .../defaultScreenOptions/index.native.ts | 10 +++++++++ .../index.ts} | 0 .../index.native.ts | 7 +++++++ .../getRightModalNavigatorOptions/index.ts | 21 +++++++++++++++++++ .../getRootNavigatorScreenOptions.js | 15 ++----------- .../getNavigationModalCardStyles/types.ts | 4 +--- 6 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 src/libs/Navigation/AppNavigator/defaultScreenOptions/index.native.ts rename src/libs/Navigation/AppNavigator/{defaultScreenOptions.js => defaultScreenOptions/index.ts} (100%) create mode 100644 src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.native.ts create mode 100644 src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.ts diff --git a/src/libs/Navigation/AppNavigator/defaultScreenOptions/index.native.ts b/src/libs/Navigation/AppNavigator/defaultScreenOptions/index.native.ts new file mode 100644 index 000000000000..12ba580c41d8 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/defaultScreenOptions/index.native.ts @@ -0,0 +1,10 @@ +const defaultScreenOptions = { + contentStyle: { + overflow: 'visible', + flex: 1, + }, + headerShown: false, + animationTypeForReplace: 'push', +}; + +export default defaultScreenOptions; diff --git a/src/libs/Navigation/AppNavigator/defaultScreenOptions.js b/src/libs/Navigation/AppNavigator/defaultScreenOptions/index.ts similarity index 100% rename from src/libs/Navigation/AppNavigator/defaultScreenOptions.js rename to src/libs/Navigation/AppNavigator/defaultScreenOptions/index.ts diff --git a/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.native.ts b/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.native.ts new file mode 100644 index 000000000000..35d4605a2e5c --- /dev/null +++ b/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.native.ts @@ -0,0 +1,7 @@ +import {NativeStackNavigationOptions} from '@react-navigation/native-stack'; + +const rightModalNavigatorOptions = (): NativeStackNavigationOptions => ({ + presentation: 'card', +}); + +export default rightModalNavigatorOptions; diff --git a/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.ts b/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.ts new file mode 100644 index 000000000000..ccab8ecd9fa5 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.ts @@ -0,0 +1,21 @@ +import {StackNavigationOptions} from '@react-navigation/stack'; +import getNavigationModalCardStyle from '../../../../styles/getNavigationModalCardStyles'; +import modalCardStyleInterpolator from '../modalCardStyleInterpolator'; + +const rightModalNavigatorOptions = (isSmallScreenWidth: boolean): StackNavigationOptions => ({ + cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, false, props), + presentation: 'transparentModal', + + // We want pop in RHP since there are some flows that would work weird otherwise + animationTypeForReplace: 'pop', + cardStyle: { + ...getNavigationModalCardStyle(), + + // This is necessary to cover translated sidebar with overlay. + width: isSmallScreenWidth ? '100%' : '200%', + // Excess space should be on the left so we need to position from right. + right: 0, + }, +}); + +export default rightModalNavigatorOptions; diff --git a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js index a7456fb071b4..ef879e269242 100644 --- a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js +++ b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js @@ -3,6 +3,7 @@ import styles from '../../../styles/styles'; import variables from '../../../styles/variables'; import getNavigationModalCardStyle from '../../../styles/getNavigationModalCardStyles'; import CONFIG from '../../../CONFIG'; +import getRightModalNavigatorOptions from './getRightModalNavigatorOptions'; const commonScreenOptions = { headerShown: false, @@ -15,19 +16,7 @@ const commonScreenOptions = { export default (isSmallScreenWidth) => ({ rightModalNavigator: { ...commonScreenOptions, - cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, false, props), - presentation: 'transparentModal', - - // We want pop in RHP since there are some flows that would work weird otherwise - animationTypeForReplace: 'pop', - cardStyle: { - ...getNavigationModalCardStyle(), - - // This is necessary to cover translated sidebar with overlay. - width: isSmallScreenWidth ? '100%' : '200%', - // Excess space should be on the left so we need to position from right. - right: 0, - }, + ...getRightModalNavigatorOptions(isSmallScreenWidth), }, homeScreen: { diff --git a/src/styles/getNavigationModalCardStyles/types.ts b/src/styles/getNavigationModalCardStyles/types.ts index 877981dd4dd2..e0dba07dc908 100644 --- a/src/styles/getNavigationModalCardStyles/types.ts +++ b/src/styles/getNavigationModalCardStyles/types.ts @@ -1,7 +1,5 @@ import {ViewStyle} from 'react-native'; -type GetNavigationModalCardStylesParams = {isSmallScreenWidth: number}; - -type GetNavigationModalCardStyles = (params: GetNavigationModalCardStylesParams) => ViewStyle; +type GetNavigationModalCardStyles = () => ViewStyle; export default GetNavigationModalCardStyles; From 27b2a131ccc8d7a9652adc127f67752f6911b447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 19 Oct 2023 17:09:36 +0200 Subject: [PATCH 005/763] add note about card* property usage --- .../Navigation/AppNavigator/getRootNavigatorScreenOptions.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js index ef879e269242..578ed1881b97 100644 --- a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js +++ b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js @@ -22,8 +22,8 @@ export default (isSmallScreenWidth) => ({ homeScreen: { title: CONFIG.SITE_TITLE, ...commonScreenOptions, + // Note: The card* properties won't be applied on mobile platforms, as they use the native defaults. cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, false, props), - cardStyle: { ...getNavigationModalCardStyle(), width: isSmallScreenWidth ? '100%' : variables.sideBarWidth, @@ -36,6 +36,7 @@ export default (isSmallScreenWidth) => ({ // eslint-disable-next-line rulesdir/no-negated-variables fullScreen: { ...commonScreenOptions, + cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, true, props), cardStyle: { ...getNavigationModalCardStyle(), @@ -49,8 +50,8 @@ export default (isSmallScreenWidth) => ({ title: CONFIG.SITE_TITLE, ...commonScreenOptions, animationEnabled: isSmallScreenWidth, - cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, true, props), + cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, true, props), cardStyle: { ...getNavigationModalCardStyle(), paddingRight: isSmallScreenWidth ? 0 : variables.sideBarWidth, From 5e5deb49b113133448114eff68fb05c8547b822d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Thu, 19 Oct 2023 17:40:19 +0200 Subject: [PATCH 006/763] migrate defaultSubRouteOptions --- .../Navigation/AppNavigator/ModalStackNavigators.js | 11 +---------- .../modalStackNavigatorOptions/index.native.ts | 9 +++++++++ .../AppNavigator/modalStackNavigatorOptions/index.ts | 10 ++++++++++ 3 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.native.ts create mode 100644 src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.ts diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 985aacc9da96..7f5e8fbc3579 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -1,17 +1,8 @@ import _ from 'underscore'; import React from 'react'; -import {CardStyleInterpolators} from '@react-navigation/stack'; import createPlatformStackNavigator from '../PlatformStackNavigation/createPlatformStackNavigator'; - -import styles from '../../../styles/styles'; import SCREENS from '../../../SCREENS'; - -// TODO: migrate options -const defaultSubRouteOptions = { - cardStyle: styles.navigationScreenCardStyle, - headerShown: false, - cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, -}; +import defaultSubRouteOptions from './modalStackNavigatorOptions'; /** * Create a modal stack navigator with an array of sub-screens. diff --git a/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.native.ts b/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.native.ts new file mode 100644 index 000000000000..c54b1d653aca --- /dev/null +++ b/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.native.ts @@ -0,0 +1,9 @@ +import {NativeStackNavigationOptions} from '@react-navigation/native-stack'; +import styles from '../../../../styles/styles'; + +const defaultSubRouteOptions: NativeStackNavigationOptions = { + contentStyle: styles.navigationScreenCardStyle, + headerShown: false, +}; + +export default defaultSubRouteOptions; diff --git a/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.ts b/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.ts new file mode 100644 index 000000000000..ea241e6f441b --- /dev/null +++ b/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.ts @@ -0,0 +1,10 @@ +import {CardStyleInterpolators, StackNavigationOptions} from '@react-navigation/stack'; +import styles from '../../../../styles/styles'; + +const defaultSubRouteOptions: StackNavigationOptions = { + cardStyle: styles.navigationScreenCardStyle, + headerShown: false, + cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS, +}; + +export default defaultSubRouteOptions; From 6e49ccf73e6aa9907a24debc676696bdb9c83c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 20 Oct 2023 08:08:20 +0200 Subject: [PATCH 007/763] change default animation for android --- .../Navigation/AppNavigator/defaultScreenOptions/index.native.ts | 1 + .../AppNavigator/getRightModalNavigatorOptions/index.native.ts | 1 + .../Navigation/AppNavigator/getRootNavigatorScreenOptions.js | 1 + .../AppNavigator/modalStackNavigatorOptions/index.native.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/src/libs/Navigation/AppNavigator/defaultScreenOptions/index.native.ts b/src/libs/Navigation/AppNavigator/defaultScreenOptions/index.native.ts index 12ba580c41d8..17100bc71bda 100644 --- a/src/libs/Navigation/AppNavigator/defaultScreenOptions/index.native.ts +++ b/src/libs/Navigation/AppNavigator/defaultScreenOptions/index.native.ts @@ -5,6 +5,7 @@ const defaultScreenOptions = { }, headerShown: false, animationTypeForReplace: 'push', + animation: 'slide_from_right', }; export default defaultScreenOptions; diff --git a/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.native.ts b/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.native.ts index 35d4605a2e5c..9c0d1fe3abff 100644 --- a/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.native.ts +++ b/src/libs/Navigation/AppNavigator/getRightModalNavigatorOptions/index.native.ts @@ -2,6 +2,7 @@ import {NativeStackNavigationOptions} from '@react-navigation/native-stack'; const rightModalNavigatorOptions = (): NativeStackNavigationOptions => ({ presentation: 'card', + animation: 'slide_from_right', }); export default rightModalNavigatorOptions; diff --git a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js index 578ed1881b97..9d485f239fe6 100644 --- a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js +++ b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.js @@ -11,6 +11,7 @@ const commonScreenOptions = { animationEnabled: true, cardOverlayEnabled: true, animationTypeForReplace: 'push', + animation: 'slide_from_right', }; export default (isSmallScreenWidth) => ({ diff --git a/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.native.ts b/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.native.ts index c54b1d653aca..96ee4c4b26d2 100644 --- a/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.native.ts +++ b/src/libs/Navigation/AppNavigator/modalStackNavigatorOptions/index.native.ts @@ -4,6 +4,7 @@ import styles from '../../../../styles/styles'; const defaultSubRouteOptions: NativeStackNavigationOptions = { contentStyle: styles.navigationScreenCardStyle, headerShown: false, + animation: 'slide_from_right', }; export default defaultSubRouteOptions; From 08c6181183ac2f092e739985a75fed309d8b65c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Fri, 20 Oct 2023 08:59:45 +0200 Subject: [PATCH 008/763] migrate overlay --- .../Navigators/{Overlay.js => Overlay/index.js} | 9 ++++----- .../AppNavigator/Navigators/Overlay/index.native.js | 7 +++++++ 2 files changed, 11 insertions(+), 5 deletions(-) rename src/libs/Navigation/AppNavigator/Navigators/{Overlay.js => Overlay/index.js} (86%) create mode 100644 src/libs/Navigation/AppNavigator/Navigators/Overlay/index.native.js diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay.js b/src/libs/Navigation/AppNavigator/Navigators/Overlay/index.js similarity index 86% rename from src/libs/Navigation/AppNavigator/Navigators/Overlay.js rename to src/libs/Navigation/AppNavigator/Navigators/Overlay/index.js index ed3c27aed673..ad35484d21d8 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/Overlay.js +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay/index.js @@ -1,14 +1,13 @@ import React from 'react'; import {Animated, View} from 'react-native'; -// TODO: migrate import {useCardAnimation} from '@react-navigation/stack'; import PropTypes from 'prop-types'; -import styles from '../../../../styles/styles'; +import styles from '../../../../../styles/styles'; -import PressableWithoutFeedback from '../../../../components/Pressable/PressableWithoutFeedback'; -import useLocalize from '../../../../hooks/useLocalize'; -import CONST from '../../../../CONST'; +import PressableWithoutFeedback from '../../../../../components/Pressable/PressableWithoutFeedback'; +import useLocalize from '../../../../../hooks/useLocalize'; +import CONST from '../../../../../CONST'; const propTypes = { /* Callback to close the modal */ diff --git a/src/libs/Navigation/AppNavigator/Navigators/Overlay/index.native.js b/src/libs/Navigation/AppNavigator/Navigators/Overlay/index.native.js new file mode 100644 index 000000000000..30651e32cbd6 --- /dev/null +++ b/src/libs/Navigation/AppNavigator/Navigators/Overlay/index.native.js @@ -0,0 +1,7 @@ +function Overlay() { + return null; +} + +Overlay.displayName = 'Overlay'; + +export default Overlay; From ea2551d0ba1181317ccd4b008a4370933ef1acfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Fija=C5=82kiewicz?= Date: Tue, 21 Nov 2023 00:10:53 +0100 Subject: [PATCH 009/763] BaseOptionsSelector refactoring --- .../OptionsSelector/BaseOptionsSelector.js | 549 +++++++++--------- 1 file changed, 290 insertions(+), 259 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index fb312125efc0..277ddb1071b8 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {Component} from 'react'; +import React, {useEffect, useMemo, useRef, useState} from 'react'; import {ScrollView, View} from 'react-native'; import _ from 'underscore'; import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager'; @@ -9,15 +9,14 @@ import FixedFooter from '@components/FixedFooter'; import FormHelpMessage from '@components/FormHelpMessage'; import OptionsList from '@components/OptionsList'; import TextInput from '@components/TextInput'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import withNavigationFocus from '@components/withNavigationFocus'; -import compose from '@libs/compose'; import getPlatform from '@libs/getPlatform'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import setSelection from '@libs/setSelection'; import styles from '@styles/styles'; import CONST from '@src/CONST'; import {defaultProps as optionsSelectorDefaultProps, propTypes as optionsSelectorPropTypes} from './optionsSelectorPropTypes'; +import {useIsFocused} from "@react-navigation/native"; +import useLocalize from "@hooks/useLocalize"; const propTypes = { /** padding bottom style of safe area */ @@ -32,11 +31,7 @@ const propTypes = { /** List styles for OptionsList */ listStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), - /** Whether navigation is focused */ - isFocused: PropTypes.bool.isRequired, - ...optionsSelectorPropTypes, - ...withLocalizePropTypes, }; const defaultProps = { @@ -48,164 +43,232 @@ const defaultProps = { ...optionsSelectorDefaultProps, }; -class BaseOptionsSelector extends Component { - constructor(props) { - super(props); - - this.updateFocusedIndex = this.updateFocusedIndex.bind(this); - this.scrollToIndex = this.scrollToIndex.bind(this); - this.selectRow = this.selectRow.bind(this); - this.selectFocusedOption = this.selectFocusedOption.bind(this); - this.addToSelection = this.addToSelection.bind(this); - this.updateSearchValue = this.updateSearchValue.bind(this); - this.relatedTarget = null; - - const allOptions = this.flattenSections(); - const focusedIndex = this.getInitiallyFocusedIndex(allOptions); - - this.state = { - allOptions, - focusedIndex, - shouldDisableRowSelection: false, - errorMessage: '', - }; +const BaseOptionsSelector = ({ + onSelectRow, + sections, + value, + onChangeText, + maxLength, + textInputLabel, + keyboardType, + placeholderText, + selectedOptions, + headerMessage, + canSelectMultipleOptions, + shouldShowMultipleOptionSelectorAsButton, + multipleOptionSelectorButtonText, + onAddToSelection, + highlightSelectedOptions, + hideSectionHeaders, + disableArrowKeysActions, + isDisabled, + boldStyle, + showTitleTooltip, + shouldPreventDefaultFocusOnSelectRow, + autoFocus, + shouldShowConfirmButton, + confirmButtonText, + onConfirmSelection, + shouldTextInputAppearBelowOptions, + shouldShowTextInput, + footerContent, + optionHoveredStyle, + sectionHeaderStyle, + shouldShowOptions, + shouldHaveOptionSeparator, + initiallyFocusedOptionKey, + shouldUseStyleForChildren, + isRowMultilineSupported, + initialFocusedIndex, + shouldTextInputInterceptSwipe, + shouldAllowScrollingChildren, + nestedScrollEnabled, +}) => { + + const isFocused = useIsFocused(); + const {translate} = useLocalize(); + + /** + * Flattens the sections into a single array of options. + * Each object in this array is enhanced to have: + * + * 1. A `sectionIndex`, which represents the index of the section it came from + * 2. An `index`, which represents the index of the option within the section it came from. + * + * @returns {Array} + */ + const flattenSections = () => { + const allOptions = []; + const calcDisabledOptionsIndexes = []; + let index = 0; + _.each(sections, (section, sectionIndex) => { + _.each(section.data, (option, optionIndex) => { + allOptions.push({ + ...option, + sectionIndex, + index: optionIndex, + }); + if (section.isDisabled || option.isDisabled) { + calcDisabledOptionsIndexes.push(index); + } + index += 1; + }); + }); + + setDisabledOptionsIndexes(calcDisabledOptionsIndexes) + return allOptions; } - componentDidMount() { - this.subscribeToKeyboardShortcut(); + /** + * @param {Array} allOptions + * @returns {Number} + */ + const getInitiallyFocusedIndex = (allOptions) => { + if (_.isNumber(initialFocusedIndex)) { + return initialFocusedIndex; + } - if (this.props.isFocused && this.props.autoFocus && this.textInput) { - this.focusTimeout = setTimeout(() => { - this.textInput.focus(); - }, CONST.ANIMATED_TRANSITION); + if (selectedOptions.length > 0) { + return selectedOptions.length; + } + const defaultIndex = shouldTextInputAppearBelowOptions ? allOptions.length : 0; + if (_.isUndefined(initiallyFocusedOptionKey)) { + return defaultIndex; + } + + const indexOfInitiallyFocusedOption = _.findIndex(allOptions, (option) => option.keyForList === initiallyFocusedOptionKey); + + if (indexOfInitiallyFocusedOption >= 0) { + return indexOfInitiallyFocusedOption; } - this.scrollToIndex(this.props.selectedOptions.length ? 0 : this.state.focusedIndex, false); + return defaultIndex; } - componentDidUpdate(prevProps) { - if (prevProps.isFocused !== this.props.isFocused) { - if (this.props.isFocused) { - this.subscribeToKeyboardShortcut(); - } else { - this.unSubscribeFromKeyboardShortcut(); + const relatedTarget = useRef(null); + const listRef = useRef(); + const textInputRef = useRef(); + const enterSubscription = useRef(); + const CTRLEnterSubscription = useRef(); + + const prevLocale = useRef(preferredLocale); + const prevSelectedOptions = useRef(selectedOptions); + + const [disabledOptionsIndexes, setDisabledOptionsIndexes] = useState([]); + + const initialAllOptions = useMemo(() => { + return flattenSections(); + },[]) + + const [allOptions, setAllOptions] = useState(initialAllOptions); + const [focusedIndex, setFocusedIndex] = useState(getInitiallyFocusedIndex(initialAllOptions)); + const [shouldDisableRowSelection, setShouldDisableRowSelection] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + + const focusTimeout = useRef(); + + useEffect(() => { + subscribeToKeyboardShortcut(); + + if (isFocused && autoFocus && textInputRef.current) { + focusTimeout.current = setTimeout(() => { + textInputRef.current.focus(); + }, CONST.ANIMATED_TRANSITION); + } + + scrollToIndex(selectedOptions.length ? 0 : focusedIndex, false); + + return () => { + if (focusTimeout.current) { + clearTimeout(focusTimeout.current); } + + unSubscribeFromKeyboardShortcut(); + } + }, []); + + + useEffect(() => { + if (isFocused) { + subscribeToKeyboardShortcut(); + } else { + unSubscribeFromKeyboardShortcut(); } // Screen coming back into focus, for example // when doing Cmd+Shift+K, then Cmd+K, then Cmd+Shift+K. // Only applies to platforms that support keyboard shortcuts - if ([CONST.PLATFORM.DESKTOP, CONST.PLATFORM.WEB].includes(getPlatform()) && !prevProps.isFocused && this.props.isFocused && this.props.autoFocus && this.textInput) { + if ([CONST.PLATFORM.DESKTOP, CONST.PLATFORM.WEB].includes(getPlatform()) && isFocused && autoFocus && textInputRef.current) { setTimeout(() => { - this.textInput.focus(); + textInputRef.current.focus(); }, CONST.ANIMATED_TRANSITION); } + }, [isFocused]) - if (_.isEqual(this.props.sections, prevProps.sections)) { - return; - } + useEffect(() => { + const newOptions = flattenSections(); - const newOptions = this.flattenSections(); + if (prevLocale.current !== preferredLocale) { + prevLocale.current = preferredLocale; + setAllOptions(newOptions); - if (prevProps.preferredLocale !== this.props.preferredLocale) { - this.setState({ - allOptions: newOptions, - }); return; } - const newFocusedIndex = this.props.selectedOptions.length; - const isNewFocusedIndex = newFocusedIndex !== this.state.focusedIndex; - - // eslint-disable-next-line react/no-did-update-set-state - this.setState( - { - allOptions: newOptions, - focusedIndex: _.isNumber(this.props.initialFocusedIndex) ? this.props.initialFocusedIndex : newFocusedIndex, - }, - () => { - // If we just toggled an option on a multi-selection page or cleared the search input, scroll to top - if (this.props.selectedOptions.length !== prevProps.selectedOptions.length || (!!prevProps.value && !this.props.value)) { - this.scrollToIndex(0); - return; - } - - // Otherwise, scroll to the focused index (as long as it's in range) - if (this.state.allOptions.length <= this.state.focusedIndex || !isNewFocusedIndex) { - return; - } - this.scrollToIndex(this.state.focusedIndex); - }, - ); - } - componentWillUnmount() { - if (this.focusTimeout) { - clearTimeout(this.focusTimeout); - } + const newFocusedIndex = selectedOptions.length; - this.unSubscribeFromKeyboardShortcut(); - } + setAllOptions(newOptions); + setFocusedIndex(_.isNumber(initialFocusedIndex) ? initialFocusedIndex : newFocusedIndex) + }, [sections]); - /** - * @param {Array} allOptions - * @returns {Number} - */ - getInitiallyFocusedIndex(allOptions) { - if (_.isNumber(this.props.initialFocusedIndex)) { - return this.props.initialFocusedIndex; - } + useEffect(() => { + const isNewFocusedIndex = selectedOptions.length !== focusedIndex; - if (this.props.selectedOptions.length > 0) { - return this.props.selectedOptions.length; - } - const defaultIndex = this.props.shouldTextInputAppearBelowOptions ? allOptions.length : 0; - if (_.isUndefined(this.props.initiallyFocusedOptionKey)) { - return defaultIndex; + // If we just toggled an option on a multi-selection page or cleared the search input, scroll to top + if (selectedOptions.length !== prevSelectedOptions.current.length || !value) { + prevSelectedOptions.current = selectedOptions; + scrollToIndex(0); + return; } - const indexOfInitiallyFocusedOption = _.findIndex(allOptions, (option) => option.keyForList === this.props.initiallyFocusedOptionKey); - - if (indexOfInitiallyFocusedOption >= 0) { - return indexOfInitiallyFocusedOption; + // Otherwise, scroll to the focused index (as long as it's in range) + if (allOptions.length <= focusedIndex || !isNewFocusedIndex) { + return; } + scrollToIndex(focusedIndex); + }, [focusedIndex]); - return defaultIndex; - } - - updateSearchValue(value) { - this.setState({ - errorMessage: value.length > this.props.maxLength ? this.props.translate('common.error.characterLimitExceedCounter', {length: value.length, limit: this.props.maxLength}) : '', - }); - - this.props.onChangeText(value); + const updateSearchValue = (value) => { + setErrorMessage(value.length > maxLength ? translate('common.error.characterLimitExceedCounter', {length: value.length, limit: maxLength}) : ''); + onChangeText(value); } - subscribeToKeyboardShortcut() { + const subscribeToKeyboardShortcut = () => { const enterConfig = CONST.KEYBOARD_SHORTCUTS.ENTER; - this.unsubscribeEnter = KeyboardShortcut.subscribe( + enterSubscription.current = KeyboardShortcut.subscribe( enterConfig.shortcutKey, - this.selectFocusedOption, + selectFocusedOption, enterConfig.descriptionKey, enterConfig.modifiers, true, - () => !this.state.allOptions[this.state.focusedIndex], + () => !allOptions[focusedIndex], ); const CTRLEnterConfig = CONST.KEYBOARD_SHORTCUTS.CTRL_ENTER; - this.unsubscribeCTRLEnter = KeyboardShortcut.subscribe( + CTRLEnterSubscription.current = KeyboardShortcut.subscribe( CTRLEnterConfig.shortcutKey, () => { - if (this.props.canSelectMultipleOptions) { - this.props.onConfirmSelection(); + if (canSelectMultipleOptions) { + onConfirmSelection(); return; } - const focusedOption = this.state.allOptions[this.state.focusedIndex]; + const focusedOption = allOptions[focusedIndex]; if (!focusedOption) { return; } - this.selectRow(focusedOption); + selectRow(focusedOption); }, CTRLEnterConfig.descriptionKey, CTRLEnterConfig.modifiers, @@ -213,83 +276,55 @@ class BaseOptionsSelector extends Component { ); } - unSubscribeFromKeyboardShortcut() { - if (this.unsubscribeEnter) { - this.unsubscribeEnter(); + const unSubscribeFromKeyboardShortcut = () => { + if (enterSubscription.current) { + enterSubscription.current(); } - if (this.unsubscribeCTRLEnter) { - this.unsubscribeCTRLEnter(); + if (CTRLEnterSubscription.current) { + CTRLEnterSubscription.current(); } } - selectFocusedOption() { - const focusedOption = this.state.allOptions[this.state.focusedIndex]; + const selectFocusedOption = () => { + const focusedOption = allOptions[focusedIndex]; - if (!focusedOption || !this.props.isFocused) { + if (!focusedOption || !isFocused) { return; } - if (this.props.canSelectMultipleOptions) { - this.selectRow(focusedOption); - } else if (!this.state.shouldDisableRowSelection) { - this.setState({shouldDisableRowSelection: true}); + if (canSelectMultipleOptions) { + selectRow(focusedOption); + } else if (!shouldDisableRowSelection) { + setShouldDisableRowSelection(true); - let result = this.selectRow(focusedOption); + let result = selectRow(focusedOption); if (!(result instanceof Promise)) { result = Promise.resolve(); } setTimeout(() => { result.finally(() => { - this.setState({shouldDisableRowSelection: false}); + setShouldDisableRowSelection(false); }); }, 500); } } - focus() { - if (!this.textInput) { + const focus = () => { + if (!textInputRef.current) { return; } - this.textInput.focus(); + textInputRef.current.focus(); } - /** - * Flattens the sections into a single array of options. - * Each object in this array is enhanced to have: - * - * 1. A `sectionIndex`, which represents the index of the section it came from - * 2. An `index`, which represents the index of the option within the section it came from. - * - * @returns {Array} - */ - flattenSections() { - const allOptions = []; - this.disabledOptionsIndexes = []; - let index = 0; - _.each(this.props.sections, (section, sectionIndex) => { - _.each(section.data, (option, optionIndex) => { - allOptions.push({ - ...option, - sectionIndex, - index: optionIndex, - }); - if (section.isDisabled || option.isDisabled) { - this.disabledOptionsIndexes.push(index); - } - index += 1; - }); - }); - return allOptions; - } /** * @param {Number} index */ - updateFocusedIndex(index) { - this.setState({focusedIndex: index}, () => this.scrollToIndex(index)); + const updateFocusedIndex = (index) => { + setFocusedIndex(index); } /** @@ -298,9 +333,9 @@ class BaseOptionsSelector extends Component { * @param {Number} index * @param {Boolean} animated */ - scrollToIndex(index, animated = true) { - const option = this.state.allOptions[index]; - if (!this.list || !option) { + const scrollToIndex = (index, animated = true) => { + const option = allOptions[index]; + if (!listRef.current || !option) { return; } @@ -312,12 +347,12 @@ class BaseOptionsSelector extends Component { // Otherwise, it will cause an index-out-of-bounds error and crash the app. let adjustedSectionIndex = sectionIndex; for (let i = 0; i < sectionIndex; i++) { - if (_.isEmpty(lodashGet(this.props.sections, `[${i}].data`))) { + if (_.isEmpty(lodashGet(sections, `[${i}].data`))) { adjustedSectionIndex--; } } - this.list.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated}); + listRef.current.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated}); } /** @@ -327,28 +362,26 @@ class BaseOptionsSelector extends Component { * @param {Object} ref * @returns {Promise} */ - selectRow(option, ref) { + const selectRow = (option, ref) => { return new Promise((resolve) => { - if (this.props.shouldShowTextInput && this.props.shouldPreventDefaultFocusOnSelectRow) { - if (this.relatedTarget && ref === this.relatedTarget) { - this.textInput.focus(); - this.relatedTarget = null; + if (shouldShowTextInput && shouldPreventDefaultFocusOnSelectRow) { + if (relatedTarget.current && ref === relatedTarget.current) { + textInputRef.current.focus(); + relatedTarget.current = null; } - if (this.textInput.isFocused()) { - setSelection(this.textInput, 0, this.props.value.length); + if (textInputRef.current.isFocused()) { + setSelection(textInputRef.current, 0, value.length); } } - const selectedOption = this.props.onSelectRow(option); + const selectedOption = onSelectRow(option); resolve(selectedOption); - if (!this.props.canSelectMultipleOptions) { + if (!canSelectMultipleOptions) { return; } // Focus the first unselected item from the list (i.e: the best result according to the current search term) - this.setState({ - focusedIndex: this.props.selectedOptions.length, - }); + setFocusedIndex(selectedOptions.length); }); } @@ -356,106 +389,105 @@ class BaseOptionsSelector extends Component { * Completes the follow-up action after clicking on multiple select button * @param {Object} option */ - addToSelection(option) { - if (this.props.shouldShowTextInput && this.props.shouldPreventDefaultFocusOnSelectRow) { - this.textInput.focus(); - if (this.textInput.isFocused()) { - setSelection(this.textInput, 0, this.props.value.length); + const addToSelection = (option) => { + if (shouldShowTextInput && shouldPreventDefaultFocusOnSelectRow) { + textInputRef.current.focus(); + if (textInputRef.current.isFocused()) { + setSelection(textInputRef.current, 0, value.length); } } - this.props.onAddToSelection(option); + onAddToSelection(option); } - render() { const shouldShowFooter = - !this.props.isReadOnly && (this.props.shouldShowConfirmButton || this.props.footerContent) && !(this.props.canSelectMultipleOptions && _.isEmpty(this.props.selectedOptions)); - const defaultConfirmButtonText = _.isUndefined(this.props.confirmButtonText) ? this.props.translate('common.confirm') : this.props.confirmButtonText; - const shouldShowDefaultConfirmButton = !this.props.footerContent && defaultConfirmButtonText; - const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : this.props.safeAreaPaddingBottomStyle; + !isReadOnly && (shouldShowConfirmButton || footerContent) && !(canSelectMultipleOptions && _.isEmpty(selectedOptions)); + const defaultConfirmButtonText = _.isUndefined(confirmButtonText) ? translate('common.confirm') : confirmButtonText; + const shouldShowDefaultConfirmButton = !footerContent && defaultConfirmButtonText; + const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : safeAreaPaddingBottomStyle; const textInput = ( (this.textInput = el)} - value={this.props.value} - label={this.props.textInputLabel} - accessibilityLabel={this.props.textInputLabel} + ref={textInputRef} + value={value} + label={textInputLabel} + accessibilityLabel={textInputLabel} accessibilityRole={CONST.ACCESSIBILITY_ROLE.TEXT} - onChangeText={this.updateSearchValue} - errorText={this.state.errorMessage} - onSubmitEditing={this.selectFocusedOption} - placeholder={this.props.placeholderText} - maxLength={this.props.maxLength + CONST.ADDITIONAL_ALLOWED_CHARACTERS} - keyboardType={this.props.keyboardType} + onChangeText={updateSearchValue} + errorText={errorMessage} + onSubmitEditing={selectFocusedOption} + placeholder={placeholderText} + maxLength={maxLength + CONST.ADDITIONAL_ALLOWED_CHARACTERS} + keyboardType={keyboardType} onBlur={(e) => { - if (!this.props.shouldPreventDefaultFocusOnSelectRow) { + if (!shouldPreventDefaultFocusOnSelectRow) { return; } - this.relatedTarget = e.relatedTarget; + relatedTarget.current = e.relatedTarget; }} selectTextOnFocus - blurOnSubmit={Boolean(this.state.allOptions.length)} + blurOnSubmit={Boolean(allOptions.length)} spellCheck={false} - shouldInterceptSwipe={this.props.shouldTextInputInterceptSwipe} - isLoading={this.props.isLoadingNewOptions} + shouldInterceptSwipe={shouldTextInputInterceptSwipe} + isLoading={isLoadingNewOptions} /> ); const optionsList = ( (this.list = el)} - optionHoveredStyle={this.props.optionHoveredStyle} - onSelectRow={this.props.onSelectRow ? this.selectRow : undefined} - sections={this.props.sections} - focusedIndex={this.state.focusedIndex} - selectedOptions={this.props.selectedOptions} - canSelectMultipleOptions={this.props.canSelectMultipleOptions} - shouldShowMultipleOptionSelectorAsButton={this.props.shouldShowMultipleOptionSelectorAsButton} - multipleOptionSelectorButtonText={this.props.multipleOptionSelectorButtonText} - onAddToSelection={this.addToSelection} - hideSectionHeaders={this.props.hideSectionHeaders} - headerMessage={this.state.errorMessage ? '' : this.props.headerMessage} - boldStyle={this.props.boldStyle} - showTitleTooltip={this.props.showTitleTooltip} - isDisabled={this.props.isDisabled} - shouldHaveOptionSeparator={this.props.shouldHaveOptionSeparator} - highlightSelectedOptions={this.props.highlightSelectedOptions} + ref={listRef} + optionHoveredStyle={optionHoveredStyle} + onSelectRow={onSelectRow ? selectRow : undefined} + sections={sections} + focusedIndex={focusedIndex} + selectedOptions={selectedOptions} + canSelectMultipleOptions={canSelectMultipleOptions} + shouldShowMultipleOptionSelectorAsButton={shouldShowMultipleOptionSelectorAsButton} + multipleOptionSelectorButtonText={multipleOptionSelectorButtonText} + onAddToSelection={addToSelection} + hideSectionHeaders={hideSectionHeaders} + headerMessage={errorMessage ? '' : headerMessage} + boldStyle={boldStyle} + showTitleTooltip={showTitleTooltip} + isDisabled={isDisabled} + shouldHaveOptionSeparator={shouldHaveOptionSeparator} + highlightSelectedOptions={highlightSelectedOptions} onLayout={() => { - if (this.props.selectedOptions.length === 0) { - this.scrollToIndex(this.state.focusedIndex, false); + if (selectedOptions.length === 0) { + scrollToIndex(focusedIndex, false); } - if (this.props.onLayout) { - this.props.onLayout(); + if (onLayout) { + onLayout(); } }} - contentContainerStyles={[safeAreaPaddingBottomStyle, ...this.props.contentContainerStyles]} - sectionHeaderStyle={this.props.sectionHeaderStyle} - listContainerStyles={this.props.listContainerStyles} - listStyles={this.props.listStyles} - isLoading={!this.props.shouldShowOptions} - showScrollIndicator={this.props.showScrollIndicator} - isRowMultilineSupported={this.props.isRowMultilineSupported} - isLoadingNewOptions={this.props.isLoadingNewOptions} - shouldPreventDefaultFocusOnSelectRow={this.props.shouldPreventDefaultFocusOnSelectRow} - nestedScrollEnabled={this.props.nestedScrollEnabled} - bounces={!this.props.shouldTextInputAppearBelowOptions || !this.props.shouldAllowScrollingChildren} + contentContainerStyles={[safeAreaPaddingBottomStyle, ...contentContainerStyles]} + sectionHeaderStyle={sectionHeaderStyle} + listContainerStyles={listContainerStyles} + listStyles={listStyles} + isLoading={!shouldShowOptions} + showScrollIndicator={showScrollIndicator} + isRowMultilineSupported={isRowMultilineSupported} + isLoadingNewOptions={isLoadingNewOptions} + shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} + nestedScrollEnabled={nestedScrollEnabled} + bounces={!shouldTextInputAppearBelowOptions || !shouldAllowScrollingChildren} /> ); const optionsAndInputsBelowThem = ( <> {optionsList} - - {this.props.children} - {this.props.shouldShowTextInput && textInput} + + {children} + {shouldShowTextInput && textInput} ); return ( {} : this.updateFocusedIndex} + disabledIndexes={disabledOptionsIndexes} + focusedIndex={focusedIndex} + maxIndex={allOptions.length - 1} + onFocusedIndexChanged={disableArrowKeysActions ? () => {} : updateFocusedIndex} shouldResetIndexOnEndReached={false} > @@ -464,7 +496,7 @@ class BaseOptionsSelector extends Component { * VirtualizedList cannot be directly nested within ScrollViews of the same orientation. * To work around this, we wrap the OptionsList component with a horizontal ScrollView. */} - {this.props.shouldTextInputAppearBelowOptions && this.props.shouldAllowScrollingChildren && ( + {shouldTextInputAppearBelowOptions && shouldAllowScrollingChildren && ( )} - {this.props.shouldTextInputAppearBelowOptions && !this.props.shouldAllowScrollingChildren && optionsAndInputsBelowThem} + {shouldTextInputAppearBelowOptions && !shouldAllowScrollingChildren && optionsAndInputsBelowThem} - {!this.props.shouldTextInputAppearBelowOptions && ( + {!shouldTextInputAppearBelowOptions && ( <> - - {this.props.children} - {this.props.shouldShowTextInput && textInput} - {Boolean(this.props.textInputAlert) && ( + + {children} + {shouldShowTextInput && textInput} + {Boolean(textInputAlert) && ( @@ -502,20 +534,19 @@ class BaseOptionsSelector extends Component { success style={[styles.w100]} text={defaultConfirmButtonText} - onPress={this.props.onConfirmSelection} + onPress={onConfirmSelection} pressOnEnter enterKeyEventListenerPriority={1} /> )} - {this.props.footerContent} + {footerContent} )} ); - } } BaseOptionsSelector.defaultProps = defaultProps; BaseOptionsSelector.propTypes = propTypes; -export default compose(withLocalize, withNavigationFocus)(BaseOptionsSelector); +export default BaseOptionsSelector; From bf38ece710ceade0b33675be3ee598c9c1bc7005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Fija=C5=82kiewicz?= Date: Tue, 21 Nov 2023 00:11:53 +0100 Subject: [PATCH 010/763] BaseOptionsSelector prettier --- .../OptionsSelector/BaseOptionsSelector.js | 328 +++++++++--------- 1 file changed, 162 insertions(+), 166 deletions(-) diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 277ddb1071b8..78803e5c546a 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -1,3 +1,4 @@ +import {useIsFocused} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useEffect, useMemo, useRef, useState} from 'react'; @@ -9,14 +10,13 @@ import FixedFooter from '@components/FixedFooter'; import FormHelpMessage from '@components/FormHelpMessage'; import OptionsList from '@components/OptionsList'; import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; import getPlatform from '@libs/getPlatform'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import setSelection from '@libs/setSelection'; import styles from '@styles/styles'; import CONST from '@src/CONST'; import {defaultProps as optionsSelectorDefaultProps, propTypes as optionsSelectorPropTypes} from './optionsSelectorPropTypes'; -import {useIsFocused} from "@react-navigation/native"; -import useLocalize from "@hooks/useLocalize"; const propTypes = { /** padding bottom style of safe area */ @@ -44,8 +44,8 @@ const defaultProps = { }; const BaseOptionsSelector = ({ - onSelectRow, - sections, + onSelectRow, + sections, value, onChangeText, maxLength, @@ -84,7 +84,6 @@ const BaseOptionsSelector = ({ shouldAllowScrollingChildren, nestedScrollEnabled, }) => { - const isFocused = useIsFocused(); const {translate} = useLocalize(); @@ -115,9 +114,9 @@ const BaseOptionsSelector = ({ }); }); - setDisabledOptionsIndexes(calcDisabledOptionsIndexes) + setDisabledOptionsIndexes(calcDisabledOptionsIndexes); return allOptions; - } + }; /** * @param {Array} allOptions @@ -143,7 +142,7 @@ const BaseOptionsSelector = ({ } return defaultIndex; - } + }; const relatedTarget = useRef(null); const listRef = useRef(); @@ -158,7 +157,7 @@ const BaseOptionsSelector = ({ const initialAllOptions = useMemo(() => { return flattenSections(); - },[]) + }, []); const [allOptions, setAllOptions] = useState(initialAllOptions); const [focusedIndex, setFocusedIndex] = useState(getInitiallyFocusedIndex(initialAllOptions)); @@ -184,10 +183,9 @@ const BaseOptionsSelector = ({ } unSubscribeFromKeyboardShortcut(); - } + }; }, []); - useEffect(() => { if (isFocused) { subscribeToKeyboardShortcut(); @@ -203,7 +201,7 @@ const BaseOptionsSelector = ({ textInputRef.current.focus(); }, CONST.ANIMATED_TRANSITION); } - }, [isFocused]) + }, [isFocused]); useEffect(() => { const newOptions = flattenSections(); @@ -218,7 +216,7 @@ const BaseOptionsSelector = ({ const newFocusedIndex = selectedOptions.length; setAllOptions(newOptions); - setFocusedIndex(_.isNumber(initialFocusedIndex) ? initialFocusedIndex : newFocusedIndex) + setFocusedIndex(_.isNumber(initialFocusedIndex) ? initialFocusedIndex : newFocusedIndex); }, [sections]); useEffect(() => { @@ -241,7 +239,7 @@ const BaseOptionsSelector = ({ const updateSearchValue = (value) => { setErrorMessage(value.length > maxLength ? translate('common.error.characterLimitExceedCounter', {length: value.length, limit: maxLength}) : ''); onChangeText(value); - } + }; const subscribeToKeyboardShortcut = () => { const enterConfig = CONST.KEYBOARD_SHORTCUTS.ENTER; @@ -274,7 +272,7 @@ const BaseOptionsSelector = ({ CTRLEnterConfig.modifiers, true, ); - } + }; const unSubscribeFromKeyboardShortcut = () => { if (enterSubscription.current) { @@ -284,7 +282,7 @@ const BaseOptionsSelector = ({ if (CTRLEnterSubscription.current) { CTRLEnterSubscription.current(); } - } + }; const selectFocusedOption = () => { const focusedOption = allOptions[focusedIndex]; @@ -309,7 +307,7 @@ const BaseOptionsSelector = ({ }); }, 500); } - } + }; const focus = () => { if (!textInputRef.current) { @@ -317,15 +315,14 @@ const BaseOptionsSelector = ({ } textInputRef.current.focus(); - } - + }; /** * @param {Number} index */ const updateFocusedIndex = (index) => { setFocusedIndex(index); - } + }; /** * Scrolls to the focused index within the SectionList @@ -353,7 +350,7 @@ const BaseOptionsSelector = ({ } listRef.current.scrollToLocation({sectionIndex: adjustedSectionIndex, itemIndex, animated}); - } + }; /** * Completes the follow-up actions after a row is selected @@ -383,7 +380,7 @@ const BaseOptionsSelector = ({ // Focus the first unselected item from the list (i.e: the best result according to the current search term) setFocusedIndex(selectedOptions.length); }); - } + }; /** * Completes the follow-up action after clicking on multiple select button @@ -397,154 +394,153 @@ const BaseOptionsSelector = ({ } } onAddToSelection(option); - } - - const shouldShowFooter = - !isReadOnly && (shouldShowConfirmButton || footerContent) && !(canSelectMultipleOptions && _.isEmpty(selectedOptions)); - const defaultConfirmButtonText = _.isUndefined(confirmButtonText) ? translate('common.confirm') : confirmButtonText; - const shouldShowDefaultConfirmButton = !footerContent && defaultConfirmButtonText; - const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : safeAreaPaddingBottomStyle; - const textInput = ( - { - if (!shouldPreventDefaultFocusOnSelectRow) { - return; - } - relatedTarget.current = e.relatedTarget; - }} - selectTextOnFocus - blurOnSubmit={Boolean(allOptions.length)} - spellCheck={false} - shouldInterceptSwipe={shouldTextInputInterceptSwipe} - isLoading={isLoadingNewOptions} - /> - ); - const optionsList = ( - { - if (selectedOptions.length === 0) { - scrollToIndex(focusedIndex, false); - } - - if (onLayout) { - onLayout(); - } - }} - contentContainerStyles={[safeAreaPaddingBottomStyle, ...contentContainerStyles]} - sectionHeaderStyle={sectionHeaderStyle} - listContainerStyles={listContainerStyles} - listStyles={listStyles} - isLoading={!shouldShowOptions} - showScrollIndicator={showScrollIndicator} - isRowMultilineSupported={isRowMultilineSupported} - isLoadingNewOptions={isLoadingNewOptions} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} - nestedScrollEnabled={nestedScrollEnabled} - bounces={!shouldTextInputAppearBelowOptions || !shouldAllowScrollingChildren} - /> - ); - - const optionsAndInputsBelowThem = ( - <> - {optionsList} - - {children} - {shouldShowTextInput && textInput} - - - ); + }; + + const shouldShowFooter = !isReadOnly && (shouldShowConfirmButton || footerContent) && !(canSelectMultipleOptions && _.isEmpty(selectedOptions)); + const defaultConfirmButtonText = _.isUndefined(confirmButtonText) ? translate('common.confirm') : confirmButtonText; + const shouldShowDefaultConfirmButton = !footerContent && defaultConfirmButtonText; + const safeAreaPaddingBottomStyle = shouldShowFooter ? undefined : safeAreaPaddingBottomStyle; + const textInput = ( + { + if (!shouldPreventDefaultFocusOnSelectRow) { + return; + } + relatedTarget.current = e.relatedTarget; + }} + selectTextOnFocus + blurOnSubmit={Boolean(allOptions.length)} + spellCheck={false} + shouldInterceptSwipe={shouldTextInputInterceptSwipe} + isLoading={isLoadingNewOptions} + /> + ); + const optionsList = ( + { + if (selectedOptions.length === 0) { + scrollToIndex(focusedIndex, false); + } - return ( - {} : updateFocusedIndex} - shouldResetIndexOnEndReached={false} - > - - {/* - * The OptionsList component uses a SectionList which uses a VirtualizedList internally. - * VirtualizedList cannot be directly nested within ScrollViews of the same orientation. - * To work around this, we wrap the OptionsList component with a horizontal ScrollView. - */} - {shouldTextInputAppearBelowOptions && shouldAllowScrollingChildren && ( - - - {optionsAndInputsBelowThem} - + if (onLayout) { + onLayout(); + } + }} + contentContainerStyles={[safeAreaPaddingBottomStyle, ...contentContainerStyles]} + sectionHeaderStyle={sectionHeaderStyle} + listContainerStyles={listContainerStyles} + listStyles={listStyles} + isLoading={!shouldShowOptions} + showScrollIndicator={showScrollIndicator} + isRowMultilineSupported={isRowMultilineSupported} + isLoadingNewOptions={isLoadingNewOptions} + shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} + nestedScrollEnabled={nestedScrollEnabled} + bounces={!shouldTextInputAppearBelowOptions || !shouldAllowScrollingChildren} + /> + ); + + const optionsAndInputsBelowThem = ( + <> + {optionsList} + + {children} + {shouldShowTextInput && textInput} + + + ); + + return ( + {} : updateFocusedIndex} + shouldResetIndexOnEndReached={false} + > + + {/* + * The OptionsList component uses a SectionList which uses a VirtualizedList internally. + * VirtualizedList cannot be directly nested within ScrollViews of the same orientation. + * To work around this, we wrap the OptionsList component with a horizontal ScrollView. + */} + {shouldTextInputAppearBelowOptions && shouldAllowScrollingChildren && ( + + + {optionsAndInputsBelowThem} - )} + + )} - {shouldTextInputAppearBelowOptions && !shouldAllowScrollingChildren && optionsAndInputsBelowThem} - - {!shouldTextInputAppearBelowOptions && ( - <> - - {children} - {shouldShowTextInput && textInput} - {Boolean(textInputAlert) && ( - - )} - - {optionsList} - - )} - - {shouldShowFooter && ( - - {shouldShowDefaultConfirmButton && ( -