diff --git a/src/App.native.tsx b/src/App.native.tsx
index 9b2940aa9d..69f7faf9e0 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() {
-
+
@@ -217,7 +217,7 @@ function App() {
-
+
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 53ca418736..808b0fc278 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 (
-
-
-
-
-
-
-
-
-
-
-
- {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index f16a9925de..c424321be7 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,21 +199,11 @@ export const ScrollableInner = React.forwardRef(
) {
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
const insets = useSafeAreaInsets()
- const {setEnabled} = useKeyboardController()
+
+ useEnableKeyboardController(isIOS)
const [keyboardHeight, setKeyboardHeight] = React.useState(0)
- React.useEffect(() => {
- if (!isIOS) {
- return
- }
-
- setEnabled(true)
- return () => {
- setEnabled(false)
- }
- })
-
useKeyboardHandler(
{
onEnd: e => {
diff --git a/src/lib/hooks/useEnableKeyboardController.tsx b/src/lib/hooks/useEnableKeyboardController.tsx
new file mode 100644
index 0000000000..c7205d0160
--- /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 (
+
+
+ {children}
+
+
+ )
+}
+
+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 (
+
+ {children}
+
+ )
+}
+
+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 ee09adaf0e..a2157d2b9c 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 9a90a1f514..b0d71b9294 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 = () => {