about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.native.tsx6
-rw-r--r--src/App.web.tsx95
-rw-r--r--src/components/Dialog/index.tsx16
-rw-r--r--src/lib/hooks/useEnableKeyboardController.tsx110
-rw-r--r--src/screens/Messages/Conversation.tsx13
-rw-r--r--src/screens/StarterPack/Wizard/index.tsx13
6 files changed, 169 insertions, 84 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 9b2940aa9..69f7faf9e 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -4,7 +4,6 @@ import '#/view/icons'
 
 import React, {useEffect, useState} from 'react'
 import {GestureHandlerRootView} from 'react-native-gesture-handler'
-import {KeyboardProvider} from 'react-native-keyboard-controller'
 import {RootSiblingParent} from 'react-native-root-siblings'
 import {
   initialWindowMetrics,
@@ -70,6 +69,7 @@ import {Splash} from '#/Splash'
 import {BottomSheetProvider} from '../modules/bottom-sheet'
 import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
 import {AppProfiler} from './AppProfiler'
+import {KeyboardControllerProvider} from './lib/hooks/useEnableKeyboardController'
 
 SplashScreen.preventAutoHideAsync()
 
@@ -188,7 +188,7 @@ function App() {
     <AppProfiler>
       <GeolocationProvider>
         <A11yProvider>
-          <KeyboardProvider enabled={false} statusBarTranslucent={true}>
+          <KeyboardControllerProvider>
             <SessionProvider>
               <PrefsStateProvider>
                 <I18nProvider>
@@ -217,7 +217,7 @@ function App() {
                 </I18nProvider>
               </PrefsStateProvider>
             </SessionProvider>
-          </KeyboardProvider>
+          </KeyboardControllerProvider>
         </A11yProvider>
       </GeolocationProvider>
     </AppProfiler>
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 53ca41873..808b0fc27 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -3,7 +3,6 @@ import '#/view/icons'
 import './style.css'
 
 import React, {useEffect, useState} from 'react'
-import {KeyboardProvider} from 'react-native-keyboard-controller'
 import {RootSiblingParent} from 'react-native-root-siblings'
 import {SafeAreaProvider} from 'react-native-safe-area-context'
 import {msg} from '@lingui/macro'
@@ -102,54 +101,52 @@ function InnerApp() {
   if (!isReady || !hasCheckedReferrer) return null
 
   return (
-    <KeyboardProvider enabled={false}>
-      <Alf theme={theme}>
-        <ThemeProvider theme={theme}>
-          <RootSiblingParent>
-            <VideoVolumeProvider>
-              <ActiveVideoProvider>
-                <React.Fragment
-                  // Resets the entire tree below when it changes:
-                  key={currentAccount?.did}>
-                  <QueryProvider currentDid={currentAccount?.did}>
-                    <ComposerProvider>
-                      <StatsigProvider>
-                        <MessagesProvider>
-                          {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-                          <LabelDefsProvider>
-                            <ModerationOptsProvider>
-                              <LoggedOutViewProvider>
-                                <SelectedFeedProvider>
-                                  <HiddenRepliesProvider>
-                                    <UnreadNotifsProvider>
-                                      <BackgroundNotificationPreferencesProvider>
-                                        <MutedThreadsProvider>
-                                          <SafeAreaProvider>
-                                            <ProgressGuideProvider>
-                                              <Shell />
-                                              <NuxDialogs />
-                                            </ProgressGuideProvider>
-                                          </SafeAreaProvider>
-                                        </MutedThreadsProvider>
-                                      </BackgroundNotificationPreferencesProvider>
-                                    </UnreadNotifsProvider>
-                                  </HiddenRepliesProvider>
-                                </SelectedFeedProvider>
-                              </LoggedOutViewProvider>
-                            </ModerationOptsProvider>
-                          </LabelDefsProvider>
-                        </MessagesProvider>
-                      </StatsigProvider>
-                    </ComposerProvider>
-                  </QueryProvider>
-                  <ToastContainer />
-                </React.Fragment>
-              </ActiveVideoProvider>
-            </VideoVolumeProvider>
-          </RootSiblingParent>
-        </ThemeProvider>
-      </Alf>
-    </KeyboardProvider>
+    <Alf theme={theme}>
+      <ThemeProvider theme={theme}>
+        <RootSiblingParent>
+          <VideoVolumeProvider>
+            <ActiveVideoProvider>
+              <React.Fragment
+                // Resets the entire tree below when it changes:
+                key={currentAccount?.did}>
+                <QueryProvider currentDid={currentAccount?.did}>
+                  <ComposerProvider>
+                    <StatsigProvider>
+                      <MessagesProvider>
+                        {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+                        <LabelDefsProvider>
+                          <ModerationOptsProvider>
+                            <LoggedOutViewProvider>
+                              <SelectedFeedProvider>
+                                <HiddenRepliesProvider>
+                                  <UnreadNotifsProvider>
+                                    <BackgroundNotificationPreferencesProvider>
+                                      <MutedThreadsProvider>
+                                        <SafeAreaProvider>
+                                          <ProgressGuideProvider>
+                                            <Shell />
+                                            <NuxDialogs />
+                                          </ProgressGuideProvider>
+                                        </SafeAreaProvider>
+                                      </MutedThreadsProvider>
+                                    </BackgroundNotificationPreferencesProvider>
+                                  </UnreadNotifsProvider>
+                                </HiddenRepliesProvider>
+                              </SelectedFeedProvider>
+                            </LoggedOutViewProvider>
+                          </ModerationOptsProvider>
+                        </LabelDefsProvider>
+                      </MessagesProvider>
+                    </StatsigProvider>
+                  </ComposerProvider>
+                </QueryProvider>
+                <ToastContainer />
+              </React.Fragment>
+            </ActiveVideoProvider>
+          </VideoVolumeProvider>
+        </RootSiblingParent>
+      </ThemeProvider>
+    </Alf>
   )
 }
 
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index f16a9925d..c424321be 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -11,7 +11,6 @@ import {
 } from 'react-native'
 import {
   KeyboardAwareScrollView,
-  useKeyboardController,
   useKeyboardHandler,
 } from 'react-native-keyboard-controller'
 import {runOnJS} from 'react-native-reanimated'
@@ -20,6 +19,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController'
 import {ScrollProvider} from '#/lib/ScrollContext'
 import {logger} from '#/logger'
 import {isAndroid, isIOS} from '#/platform/detection'
@@ -199,20 +199,10 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
   ) {
     const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
     const insets = useSafeAreaInsets()
-    const {setEnabled} = useKeyboardController()
 
-    const [keyboardHeight, setKeyboardHeight] = React.useState(0)
-
-    React.useEffect(() => {
-      if (!isIOS) {
-        return
-      }
+    useEnableKeyboardController(isIOS)
 
-      setEnabled(true)
-      return () => {
-        setEnabled(false)
-      }
-    })
+    const [keyboardHeight, setKeyboardHeight] = React.useState(0)
 
     useKeyboardHandler(
       {
diff --git a/src/lib/hooks/useEnableKeyboardController.tsx b/src/lib/hooks/useEnableKeyboardController.tsx
new file mode 100644
index 000000000..c7205d016
--- /dev/null
+++ b/src/lib/hooks/useEnableKeyboardController.tsx
@@ -0,0 +1,110 @@
+import {
+  createContext,
+  useCallback,
+  useContext,
+  useEffect,
+  useMemo,
+  useRef,
+} from 'react'
+import {
+  KeyboardProvider,
+  useKeyboardController,
+} from 'react-native-keyboard-controller'
+import {useFocusEffect} from '@react-navigation/native'
+
+import {IS_DEV} from '#/env'
+
+const KeyboardControllerRefCountContext = createContext<{
+  incrementRefCount: () => void
+  decrementRefCount: () => void
+}>({
+  incrementRefCount: () => {},
+  decrementRefCount: () => {},
+})
+
+export function KeyboardControllerProvider({
+  children,
+}: {
+  children: React.ReactNode
+}) {
+  return (
+    <KeyboardProvider
+      enabled={false}
+      // I don't think this is necessary, but Chesterton's fence and all that -sfn
+      statusBarTranslucent={true}>
+      <KeyboardControllerProviderInner>
+        {children}
+      </KeyboardControllerProviderInner>
+    </KeyboardProvider>
+  )
+}
+
+function KeyboardControllerProviderInner({
+  children,
+}: {
+  children: React.ReactNode
+}) {
+  const {setEnabled} = useKeyboardController()
+  const refCount = useRef(0)
+
+  const value = useMemo(
+    () => ({
+      incrementRefCount: () => {
+        refCount.current++
+        setEnabled(refCount.current > 0)
+      },
+      decrementRefCount: () => {
+        refCount.current--
+        setEnabled(refCount.current > 0)
+
+        if (IS_DEV && refCount.current < 0) {
+          console.error('KeyboardController ref count < 0')
+        }
+      },
+    }),
+    [setEnabled],
+  )
+
+  return (
+    <KeyboardControllerRefCountContext.Provider value={value}>
+      {children}
+    </KeyboardControllerRefCountContext.Provider>
+  )
+}
+
+export function useEnableKeyboardController(shouldEnable: boolean) {
+  const {incrementRefCount, decrementRefCount} = useContext(
+    KeyboardControllerRefCountContext,
+  )
+
+  useEffect(() => {
+    if (!shouldEnable) {
+      return
+    }
+    incrementRefCount()
+    return () => {
+      decrementRefCount()
+    }
+  }, [shouldEnable, incrementRefCount, decrementRefCount])
+}
+
+/**
+ * Like `useEnableKeyboardController`, but using `useFocusEffect`
+ */
+export function useEnableKeyboardControllerScreen(shouldEnable: boolean) {
+  const {incrementRefCount, decrementRefCount} = useContext(
+    KeyboardControllerRefCountContext,
+  )
+
+  useFocusEffect(
+    useCallback(() => {
+      if (!shouldEnable) {
+        return
+      }
+      incrementRefCount()
+      return () => {
+        decrementRefCount()
+      }
+    }, [shouldEnable, incrementRefCount, decrementRefCount]),
+  )
+}
diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx
index ee09adaf0..a2157d2b9 100644
--- a/src/screens/Messages/Conversation.tsx
+++ b/src/screens/Messages/Conversation.tsx
@@ -1,6 +1,5 @@
 import React, {useCallback} from 'react'
 import {View} from 'react-native'
-import {useKeyboardController} from 'react-native-keyboard-controller'
 import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -8,6 +7,7 @@ import {useFocusEffect, useNavigation} from '@react-navigation/native'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
 
 import {useEmail} from '#/lib/hooks/useEmail'
+import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
 import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
 import {isWeb} from '#/platform/detection'
 import {useProfileShadow} from '#/state/cache/profile-shadow'
@@ -39,16 +39,7 @@ export function MessagesConversationScreen({route}: Props) {
   const convoId = route.params.conversation
   const {setCurrentConvoId} = useCurrentConvoId()
 
-  const {setEnabled} = useKeyboardController()
-  useFocusEffect(
-    useCallback(() => {
-      if (isWeb) return
-      setEnabled(true)
-      return () => {
-        setEnabled(false)
-      }
-    }, [setEnabled]),
-  )
+  useEnableKeyboardControllerScreen(true)
 
   useFocusEffect(
     useCallback(() => {
diff --git a/src/screens/StarterPack/Wizard/index.tsx b/src/screens/StarterPack/Wizard/index.tsx
index 9a90a1f51..b0d71b929 100644
--- a/src/screens/StarterPack/Wizard/index.tsx
+++ b/src/screens/StarterPack/Wizard/index.tsx
@@ -1,9 +1,6 @@
 import React from 'react'
 import {Keyboard, TouchableOpacity, View} from 'react-native'
-import {
-  KeyboardAwareScrollView,
-  useKeyboardController,
-} from 'react-native-keyboard-controller'
+import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {Image} from 'expo-image'
 import {
@@ -20,6 +17,7 @@ import {useFocusEffect, useNavigation} from '@react-navigation/native'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
 
 import {HITSLOP_10, STARTER_PACK_MAX_SIZE} from '#/lib/constants'
+import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
 import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
 import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
 import {logEvent} from '#/lib/statsig/statsig'
@@ -151,7 +149,6 @@ function WizardInner({
   const {_} = useLingui()
   const t = useTheme()
   const setMinimalShellMode = useSetMinimalShellMode()
-  const {setEnabled} = useKeyboardController()
   const [state, dispatch] = useWizardState()
   const {currentAccount} = useSession()
   const {data: currentProfile} = useProfileQuery({
@@ -166,16 +163,16 @@ function WizardInner({
     })
   }, [navigation])
 
+  useEnableKeyboardControllerScreen(true)
+
   useFocusEffect(
     React.useCallback(() => {
-      setEnabled(true)
       setMinimalShellMode(true)
 
       return () => {
         setMinimalShellMode(false)
-        setEnabled(false)
       }
-    }, [setMinimalShellMode, setEnabled]),
+    }, [setMinimalShellMode]),
   )
 
   const getDefaultName = () => {