From ad3dd9f6dccaa4b73da0000f41f23ac2fea5d1b2 Mon Sep 17 00:00:00 2001 From: Hailey Date: Wed, 20 Mar 2024 17:26:38 -0700 Subject: [PATCH] Fix problems with `BottomSheet` and the report dialog (#3297) * use @discord/bottom-sheet * add @types/invariant * some progress on keyboard dialog * rework rework add a comment use discord bottom sheet * remove `@gorhom/bottom-sheet` * remove android specific code * organize imports --- package.json | 3 +- src/App.native.tsx | 77 +++++++++---------- src/components/Dialog/index.tsx | 48 +++++------- src/components/Dialog/types.ts | 2 +- src/components/ReportDialog/index.tsx | 31 ++++---- src/components/hooks/useOnKeyboard.ts | 12 +++ src/view/com/modals/Modal.tsx | 42 +++++----- src/view/com/modals/SwitchAccount.tsx | 25 +++--- src/view/com/modals/util.tsx | 2 +- .../com/util/BottomSheetCustomBackdrop.tsx | 2 +- yarn.lock | 20 +++-- 11 files changed, 138 insertions(+), 126 deletions(-) create mode 100644 src/components/hooks/useOnKeyboard.ts diff --git a/package.json b/package.json index eaf242a5..9baf7df1 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@atproto/api": "^0.12.2", "@bam.tech/react-native-image-resizer": "^3.0.4", "@braintree/sanitize-url": "^6.0.2", + "@discord/bottom-sheet": "https://github.com/bluesky-social/react-native-bottom-sheet.git#discord-fork-4.6.1", "@emoji-mart/react": "^1.1.1", "@expo/html-elements": "^0.4.2", "@expo/webpack-config": "^19.0.0", @@ -56,7 +57,6 @@ "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/react-native-fontawesome": "^0.3.0", - "@gorhom/bottom-sheet": "^4.5.1", "@lingui/react": "^4.5.0", "@mattermost/react-native-paste-input": "^0.6.4", "@miblanchard/react-native-slider": "^2.3.1", @@ -93,6 +93,7 @@ "@tiptap/pm": "^2.0.0-beta.220", "@tiptap/react": "^2.0.0-beta.220", "@tiptap/suggestion": "^2.0.0-beta.220", + "@types/invariant": "^2.2.37", "@types/node": "^18.16.2", "@zxing/text-encoding": "^0.9.0", "array.prototype.findlast": "^1.2.3", diff --git a/src/App.native.tsx b/src/App.native.tsx index e825ffa0..f2e0c4f7 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -1,59 +1,58 @@ import 'react-native-url-polyfill/auto' import 'lib/sentry' // must be near top - -import React, {useState, useEffect} from 'react' -import {RootSiblingParent} from 'react-native-root-siblings' -import * as SplashScreen from 'expo-splash-screen' -import {GestureHandlerRootView} from 'react-native-gesture-handler' -import {PersistQueryClientProvider} from '@tanstack/react-query-persist-client' -import { - SafeAreaProvider, - initialWindowMetrics, -} from 'react-native-safe-area-context' - import 'view/icons' -import {ThemeProvider as Alf} from '#/alf' -import {useColorModeTheme} from '#/alf/util/useColorModeTheme' -import {init as initPersistedState} from '#/state/persisted' -import {listenSessionDropped} from './state/events' -import {ThemeProvider} from 'lib/ThemeContext' -import {s} from 'lib/styles' -import {Shell} from 'view/shell' -import * as notifications from 'lib/notifications/notifications' -import * as Toast from 'view/com/util/Toast' +import React, {useEffect, useState} from 'react' +import {GestureHandlerRootView} from 'react-native-gesture-handler' +import {RootSiblingParent} from 'react-native-root-siblings' +import { + initialWindowMetrics, + SafeAreaProvider, +} from 'react-native-safe-area-context' +import * as SplashScreen from 'expo-splash-screen' +import {StatusBar} from 'expo-status-bar' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {PersistQueryClientProvider} from '@tanstack/react-query-persist-client' + +import {Provider as StatsigProvider} from '#/lib/statsig/statsig' +import {init as initPersistedState} from '#/state/persisted' +import * as persisted from '#/state/persisted' +import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs' +import {useIntentHandler} from 'lib/hooks/useIntentHandler' +import * as notifications from 'lib/notifications/notifications' import { - queryClient, asyncStoragePersister, dehydrateOptions, + queryClient, } from 'lib/react-query' -import {TestCtrls} from 'view/com/testing/TestCtrls' -import {Provider as ShellStateProvider} from 'state/shell' -import {Provider as ModalStateProvider} from 'state/modals' +import {s} from 'lib/styles' +import {ThemeProvider} from 'lib/ThemeContext' +import {isAndroid} from 'platform/detection' import {Provider as DialogStateProvider} from 'state/dialogs' -import {Provider as LightboxStateProvider} from 'state/lightbox' -import {Provider as MutedThreadsProvider} from 'state/muted-threads' import {Provider as InvitesStateProvider} from 'state/invites' +import {Provider as LightboxStateProvider} from 'state/lightbox' +import {Provider as ModalStateProvider} from 'state/modals' +import {Provider as MutedThreadsProvider} from 'state/muted-threads' import {Provider as PrefsStateProvider} from 'state/preferences' -import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' -import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed' -import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs' -import I18nProvider from './locale/i18nProvider' +import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' import { Provider as SessionProvider, useSession, useSessionApi, } from 'state/session' -import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' -import * as persisted from '#/state/persisted' -import {Splash} from '#/Splash' +import {Provider as ShellStateProvider} from 'state/shell' +import {Provider as LoggedOutViewProvider} from 'state/shell/logged-out' +import {Provider as SelectedFeedProvider} from 'state/shell/selected-feed' +import {TestCtrls} from 'view/com/testing/TestCtrls' +import * as Toast from 'view/com/util/Toast' +import {Shell} from 'view/shell' +import {ThemeProvider as Alf} from '#/alf' +import {useColorModeTheme} from '#/alf/util/useColorModeTheme' import {Provider as PortalProvider} from '#/components/Portal' -import {Provider as StatsigProvider} from '#/lib/statsig/statsig' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useIntentHandler} from 'lib/hooks/useIntentHandler' -import {StatusBar} from 'expo-status-bar' -import {isAndroid} from 'platform/detection' +import {Splash} from '#/Splash' +import I18nProvider from './locale/i18nProvider' +import {listenSessionDropped} from './state/events' SplashScreen.preventAutoHideAsync() diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index a85a1c4f..07e101f8 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -1,31 +1,31 @@ import React, {useImperativeHandle} from 'react' -import {View, Dimensions, Keyboard, Pressable} from 'react-native' +import {Dimensions, Pressable, View} from 'react-native' +import Animated, {useAnimatedStyle} from 'react-native-reanimated' +import {useSafeAreaInsets} from 'react-native-safe-area-context' import BottomSheet, { BottomSheetBackdropProps, BottomSheetScrollView, + BottomSheetScrollViewMethods, BottomSheetTextInput, BottomSheetView, useBottomSheet, WINDOW_HEIGHT, -} from '@gorhom/bottom-sheet' -import {useSafeAreaInsets} from 'react-native-safe-area-context' -import Animated, {useAnimatedStyle} from 'react-native-reanimated' +} from '@discord/bottom-sheet/src' -import {useTheme, atoms as a, flatten} from '#/alf' -import {Portal} from '#/components/Portal' -import {createInput} from '#/components/forms/TextField' import {logger} from '#/logger' import {useDialogStateControlContext} from '#/state/dialogs' - +import {isNative} from 'platform/detection' +import {atoms as a, flatten, useTheme} from '#/alf' +import {Context} from '#/components/Dialog/context' import { - DialogOuterProps, DialogControlProps, DialogInnerProps, + DialogOuterProps, } from '#/components/Dialog/types' -import {Context} from '#/components/Dialog/context' -import {isNative} from 'platform/detection' +import {createInput} from '#/components/forms/TextField' +import {Portal} from '#/components/Portal' -export {useDialogControl, useDialogContext} from '#/components/Dialog/context' +export {useDialogContext, useDialogControl} from '#/components/Dialog/context' export * from '#/components/Dialog/types' // @ts-ignore export const Input = createInput(BottomSheetTextInput) @@ -122,7 +122,6 @@ export function Outer({ ) const onCloseInner = React.useCallback(() => { - Keyboard.dismiss() try { closeCallback.current?.() } catch (e: any) { @@ -206,16 +205,14 @@ export function Inner({children, style}: DialogInnerProps) { ) } -export function ScrollableInner({ - children, - keyboardDismissMode, - style, -}: DialogInnerProps) { +export const ScrollableInner = React.forwardRef< + BottomSheetScrollViewMethods, + DialogInnerProps +>(function ScrollableInner({children, style}, ref) { const insets = useSafeAreaInsets() return ( + contentContainerStyle={isNative ? a.pb_4xl : undefined} + ref={ref}> {children} ) -} +}) export function Handle() { const t = useTheme() - const onTouchStart = React.useCallback(() => { - Keyboard.dismiss() - }, []) - return ( - + (null) + useOnKeyboardDidShow(() => { + ref.current?.scrollToEnd({animated: true}) + }) + return ( - + {isLoading ? ( @@ -55,8 +60,6 @@ function ReportDialogInner(props: ReportDialogProps) { ) : ( )} - - ) } diff --git a/src/components/hooks/useOnKeyboard.ts b/src/components/hooks/useOnKeyboard.ts new file mode 100644 index 00000000..5de681a4 --- /dev/null +++ b/src/components/hooks/useOnKeyboard.ts @@ -0,0 +1,12 @@ +import React from 'react' +import {Keyboard} from 'react-native' + +export function useOnKeyboardDidShow(cb: () => unknown) { + React.useEffect(() => { + const subscription = Keyboard.addListener('keyboardDidShow', cb) + + return () => { + subscription.remove() + } + }, [cb]) +} diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 238cfc50..af86f13a 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -1,33 +1,33 @@ -import React, {useRef, useEffect} from 'react' +import React, {useEffect, useRef} from 'react' import {StyleSheet} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' -import BottomSheet from '@gorhom/bottom-sheet' -import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' -import {usePalette} from 'lib/hooks/usePalette' +import BottomSheet from '@discord/bottom-sheet/src' -import {useModals, useModalControls} from '#/state/modals' -import * as EditProfileModal from './EditProfile' -import * as RepostModal from './Repost' -import * as SelfLabelModal from './SelfLabel' -import * as ThreadgateModal from './Threadgate' -import * as CreateOrEditListModal from './CreateOrEditList' -import * as UserAddRemoveListsModal from './UserAddRemoveLists' -import * as ListAddUserModal from './ListAddRemoveUsers' +import {useModalControls, useModals} from '#/state/modals' +import {usePalette} from 'lib/hooks/usePalette' +import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' +import * as AddAppPassword from './AddAppPasswords' import * as AltImageModal from './AltImage' import * as EditImageModal from './AltImage' -import * as DeleteAccountModal from './DeleteAccount' -import * as ChangeHandleModal from './ChangeHandle' -import * as InviteCodesModal from './InviteCodes' -import * as AddAppPassword from './AddAppPasswords' -import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' -import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' -import * as VerifyEmailModal from './VerifyEmail' import * as ChangeEmailModal from './ChangeEmail' +import * as ChangeHandleModal from './ChangeHandle' import * as ChangePasswordModal from './ChangePassword' -import * as SwitchAccountModal from './SwitchAccount' -import * as LinkWarningModal from './LinkWarning' +import * as CreateOrEditListModal from './CreateOrEditList' +import * as DeleteAccountModal from './DeleteAccount' +import * as EditProfileModal from './EditProfile' import * as EmbedConsentModal from './EmbedConsent' import * as InAppBrowserConsentModal from './InAppBrowserConsent' +import * as InviteCodesModal from './InviteCodes' +import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' +import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' +import * as LinkWarningModal from './LinkWarning' +import * as ListAddUserModal from './ListAddRemoveUsers' +import * as RepostModal from './Repost' +import * as SelfLabelModal from './SelfLabel' +import * as SwitchAccountModal from './SwitchAccount' +import * as ThreadgateModal from './Threadgate' +import * as UserAddRemoveListsModal from './UserAddRemoveLists' +import * as VerifyEmailModal from './VerifyEmail' const DEFAULT_SNAPPOINTS = ['90%'] const HANDLE_HEIGHT = 24 diff --git a/src/view/com/modals/SwitchAccount.tsx b/src/view/com/modals/SwitchAccount.tsx index 892b07c9..03bef719 100644 --- a/src/view/com/modals/SwitchAccount.tsx +++ b/src/view/com/modals/SwitchAccount.tsx @@ -5,22 +5,23 @@ import { TouchableOpacity, View, } from 'react-native' -import {Text} from '../util/text/Text' -import {s} from 'lib/styles' -import {usePalette} from 'lib/hooks/usePalette' +import {BottomSheetScrollView} from '@discord/bottom-sheet/src' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useProfileQuery} from '#/state/queries/profile' +import {SessionAccount, useSession, useSessionApi} from '#/state/session' +import {useCloseAllActiveElements} from '#/state/util' import {useAnalytics} from 'lib/analytics/analytics' +import {Haptics} from 'lib/haptics' import {useAccountSwitcher} from 'lib/hooks/useAccountSwitcher' -import {UserAvatar} from '../util/UserAvatar' +import {usePalette} from 'lib/hooks/usePalette' +import {makeProfileLink} from 'lib/routes/links' +import {s} from 'lib/styles' import {AccountDropdownBtn} from '../util/AccountDropdownBtn' import {Link} from '../util/Link' -import {makeProfileLink} from 'lib/routes/links' -import {BottomSheetScrollView} from '@gorhom/bottom-sheet' -import {Haptics} from 'lib/haptics' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useSession, useSessionApi, SessionAccount} from '#/state/session' -import {useProfileQuery} from '#/state/queries/profile' -import {useCloseAllActiveElements} from '#/state/util' +import {Text} from '../util/text/Text' +import {UserAvatar} from '../util/UserAvatar' export const snapPoints = ['40%', '90%'] diff --git a/src/view/com/modals/util.tsx b/src/view/com/modals/util.tsx index 06f394ec..c047a052 100644 --- a/src/view/com/modals/util.tsx +++ b/src/view/com/modals/util.tsx @@ -1,4 +1,4 @@ export { BottomSheetScrollView as ScrollView, BottomSheetTextInput as TextInput, -} from '@gorhom/bottom-sheet' +} from '@discord/bottom-sheet/src' diff --git a/src/view/com/util/BottomSheetCustomBackdrop.tsx b/src/view/com/util/BottomSheetCustomBackdrop.tsx index ab657025..0d15c5e5 100644 --- a/src/view/com/util/BottomSheetCustomBackdrop.tsx +++ b/src/view/com/util/BottomSheetCustomBackdrop.tsx @@ -1,11 +1,11 @@ import React, {useMemo} from 'react' import {TouchableWithoutFeedback} from 'react-native' -import {BottomSheetBackdropProps} from '@gorhom/bottom-sheet' import Animated, { Extrapolate, interpolate, useAnimatedStyle, } from 'react-native-reanimated' +import {BottomSheetBackdropProps} from '@discord/bottom-sheet/src' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' diff --git a/yarn.lock b/yarn.lock index 286ddfd5..c81f1206 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2814,6 +2814,13 @@ pino "^8.11.0" pino-http "^8.3.3" +"@discord/bottom-sheet@https://github.com/bluesky-social/react-native-bottom-sheet.git#discord-fork-4.6.1": + version "4.6.1" + resolved "https://github.com/bluesky-social/react-native-bottom-sheet.git#54dc2e0e318b0524a2d2d8fb817f6c48101bb0b1" + dependencies: + "@gorhom/portal" "1.0.14" + invariant "^2.2.4" + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -3602,14 +3609,6 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@gorhom/bottom-sheet@^4.5.1": - version "4.5.1" - resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-4.5.1.tgz#1ac4b234a80e7dff263f0b7ac207f92e41562849" - integrity sha512-4Qy6hzvN32fXu2hDxDXOIS0IBGBT6huST7J7+K1V5bXemZ08KIx5ZffyLgwhCUl+CnyeG2KG6tqk6iYLkIwi7Q== - dependencies: - "@gorhom/portal" "1.0.14" - invariant "^2.2.4" - "@gorhom/portal@1.0.14": version "1.0.14" resolved "https://registry.yarnpkg.com/@gorhom/portal/-/portal-1.0.14.tgz#1953edb76aaba80fb24021dc774550194a18e111" @@ -7629,6 +7628,11 @@ dependencies: "@types/node" "*" +"@types/invariant@^2.2.37": + version "2.2.37" + resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.37.tgz#1709741e534364d653c87dff22fc76fa94aa7bc0" + integrity sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"