Bluesky app fork with some witchin' additions 💫

Android sheets edge to edge (#8342)

authored by samuel.fm and committed by

GitHub 338016ed e2058c8e

+161 -228
+1 -1
modules/bottom-sheet/android/build.gradle
··· 44 44 45 45 dependencies { 46 46 implementation project(':expo-modules-core') 47 - implementation 'com.google.android.material:material:1.12.0' 47 + implementation 'com.google.android.material:material:1.13.0' 48 48 implementation "com.facebook.react:react-native:+" 49 49 }
+115 -85
modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/BottomSheetView.kt
··· 5 5 import android.view.View 6 6 import android.view.ViewGroup 7 7 import android.view.ViewStructure 8 + import android.view.Window 8 9 import android.view.accessibility.AccessibilityEvent 9 10 import android.widget.FrameLayout 11 + import androidx.core.view.ViewCompat 12 + import androidx.core.view.WindowInsetsCompat 13 + import androidx.core.view.WindowInsetsControllerCompat 10 14 import androidx.core.view.allViews 11 15 import com.facebook.react.bridge.LifecycleEventListener 12 16 import com.facebook.react.bridge.ReactContext ··· 15 19 import com.facebook.react.uimanager.events.EventDispatcher 16 20 import com.google.android.material.bottomsheet.BottomSheetBehavior 17 21 import com.google.android.material.bottomsheet.BottomSheetDialog 22 + import com.google.android.material.internal.EdgeToEdgeUtils 18 23 import expo.modules.kotlin.AppContext 19 24 import expo.modules.kotlin.viewevent.EventDispatcher 20 25 import expo.modules.kotlin.views.ExpoView ··· 29 34 30 35 private lateinit var dialogRootViewGroup: DialogRootViewGroup 31 36 private var eventDispatcher: EventDispatcher? = null 37 + private var isKeyboardVisible: Boolean = false 32 38 33 - private val rawScreenHeight = 39 + private val screenHeight = 34 40 context.resources.displayMetrics.heightPixels 35 41 .toFloat() 36 - private val safeScreenHeight = (rawScreenHeight - getNavigationBarHeight()).toFloat() 37 42 38 43 private fun getNavigationBarHeight(): Int { 39 44 val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android") 40 45 return if (resourceId > 0) resources.getDimensionPixelSize(resourceId) else 0 41 46 } 42 47 48 + private fun getStatusBarHeight(): Int { 49 + val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") 50 + return if (resourceId > 0) resources.getDimensionPixelSize(resourceId) else 0 51 + } 52 + 43 53 private val onAttemptDismiss by EventDispatcher() 44 54 private val onSnapPointChange by EventDispatcher() 45 55 private val onStateChange by EventDispatcher() 46 56 47 - // Props 48 57 var disableDrag = false 49 58 set(value) { 50 59 field = value ··· 56 65 field = value 57 66 this.dialog?.setCancelable(!value) 58 67 } 68 + 59 69 var preventExpansion = false 60 70 61 71 var minHeight = 0f 62 72 set(value) { 63 - field = 64 - if (value < 0) { 65 - 0f 66 - } else { 67 - dpToPx(value) 68 - } 73 + field = if (value < 0) 0f else dpToPx(value) 69 74 } 70 75 71 - var maxHeight = this.safeScreenHeight 76 + var maxHeight = this.screenHeight 72 77 set(value) { 73 78 val px = dpToPx(value) 74 - field = 75 - if (px > this.safeScreenHeight) { 76 - this.safeScreenHeight 77 - } else { 78 - px 79 - } 79 + field = if (px > this.screenHeight) this.screenHeight else px 80 80 } 81 81 82 82 private var isOpen: Boolean = false 83 83 set(value) { 84 84 field = value 85 - onStateChange( 86 - mapOf( 87 - "state" to if (value) "open" else "closed", 88 - ), 89 - ) 85 + onStateChange(mapOf("state" to if (value) "open" else "closed")) 90 86 } 91 87 92 88 private var isOpening: Boolean = false 93 89 set(value) { 94 90 field = value 95 91 if (value) { 96 - onStateChange( 97 - mapOf( 98 - "state" to "opening", 99 - ), 100 - ) 92 + onStateChange(mapOf("state" to "opening")) 101 93 } 102 94 } 103 95 ··· 105 97 set(value) { 106 98 field = value 107 99 if (value) { 108 - onStateChange( 109 - mapOf( 110 - "state" to "closing", 111 - ), 112 - ) 100 + onStateChange(mapOf("state" to "closing")) 113 101 } 114 102 } 115 103 116 104 private var selectedSnapPoint = 0 117 105 set(value) { 118 106 if (field == value) return 119 - 120 107 field = value 121 - onSnapPointChange( 122 - mapOf( 123 - "snapPoint" to value, 124 - ), 125 - ) 108 + onSnapPointChange(mapOf("snapPoint" to value)) 126 109 } 127 - 128 - // Lifecycle 129 110 130 111 init { 131 112 (appContext.reactContext as? ReactContext)?.let { 132 113 it.addLifecycleEventListener(this) 133 114 this.eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(it, this.id) 134 - 135 115 this.dialogRootViewGroup = DialogRootViewGroup(context) 136 116 this.dialogRootViewGroup.eventDispatcher = this.eventDispatcher 137 117 } ··· 161 141 private fun getHalfExpandedRatio(contentHeight: Float): Float = 162 142 when { 163 143 // Full height sheets 164 - contentHeight >= safeScreenHeight -> 0.99f 165 - // Medium height sheets (>50% but <100%) 166 - contentHeight >= safeScreenHeight / 2 -> 167 - this.clampRatio(this.getTargetHeight() / safeScreenHeight) 168 - // Small height sheets (<50%) 169 - else -> 170 - this.clampRatio(this.getTargetHeight() / rawScreenHeight) 144 + contentHeight >= screenHeight -> 0.99f 145 + else -> this.clampRatio(this.getTargetHeight() / screenHeight) 171 146 } 172 147 173 148 private fun present() { 174 149 if (this.isOpen || this.isOpening || this.isClosing) return 175 150 176 151 val contentHeight = this.getContentHeight() 177 - val dialog = BottomSheetDialog(context) 152 + 153 + var activityWindow: Window? = null 154 + var currentContext = context 155 + while (currentContext != null) { 156 + if (currentContext is android.app.Activity) { 157 + activityWindow = currentContext.window 158 + break 159 + } 160 + currentContext = (currentContext as? android.content.ContextWrapper)?.baseContext 161 + } 162 + 163 + val originalStatusBarAppearance = 164 + activityWindow?.let { window -> 165 + WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars 166 + } 167 + val originalNavBarAppearance = 168 + activityWindow?.let { window -> 169 + WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightNavigationBars 170 + } 171 + 172 + val dialog = BottomSheetDialog(context, R.style.EdgeToEdgeBottomSheetDialogTheme) 178 173 dialog.setContentView(dialogRootViewGroup) 179 174 dialog.setCancelable(!preventDismiss) 175 + dialog.setDismissWithAnimation(true) 180 176 dialog.setOnDismissListener { 181 177 this.isClosing = true 182 178 this.destroy() 183 179 } 184 180 181 + dialog.setOnShowListener { 182 + dialog.window?.let { window -> 183 + val insetsController = WindowInsetsControllerCompat(window, window.decorView) 184 + if (originalNavBarAppearance != null) { 185 + insetsController.isAppearanceLightNavigationBars = originalNavBarAppearance 186 + } 187 + if (originalStatusBarAppearance != null) { 188 + EdgeToEdgeUtils.setLightStatusBar(window, originalStatusBarAppearance) 189 + } 190 + } 191 + } 192 + 185 193 val bottomSheet = dialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet) 186 194 bottomSheet?.let { 187 195 it.setBackgroundColor(0) ··· 194 202 behavior.isDraggable = true 195 203 behavior.isHideable = true 196 204 197 - if (contentHeight >= this.safeScreenHeight || this.minHeight >= this.safeScreenHeight) { 205 + if (preventExpansion) { 206 + behavior.maxHeight = (behavior.halfExpandedRatio * screenHeight).toInt() 207 + } else { 208 + behavior.maxHeight = (screenHeight - getStatusBarHeight()).toInt() 209 + } 210 + 211 + val targetHeight = this.getTargetHeight() 212 + val availableHeight = screenHeight - getStatusBarHeight() - getNavigationBarHeight() 213 + val shouldBeExpanded = targetHeight >= availableHeight 214 + 215 + if (shouldBeExpanded) { 198 216 behavior.state = BottomSheetBehavior.STATE_EXPANDED 199 217 this.selectedSnapPoint = 2 200 218 } else { ··· 209 227 newState: Int, 210 228 ) { 211 229 when (newState) { 212 - BottomSheetBehavior.STATE_EXPANDED -> { 213 - selectedSnapPoint = 2 214 - } 215 - BottomSheetBehavior.STATE_COLLAPSED -> { 216 - selectedSnapPoint = 1 217 - } 218 - BottomSheetBehavior.STATE_HALF_EXPANDED -> { 219 - selectedSnapPoint = 1 220 - } 221 - BottomSheetBehavior.STATE_HIDDEN -> { 222 - selectedSnapPoint = 0 223 - } 230 + BottomSheetBehavior.STATE_EXPANDED -> selectedSnapPoint = 2 231 + BottomSheetBehavior.STATE_COLLAPSED -> selectedSnapPoint = 1 232 + BottomSheetBehavior.STATE_HALF_EXPANDED -> selectedSnapPoint = 1 233 + BottomSheetBehavior.STATE_HIDDEN -> selectedSnapPoint = 0 224 234 } 225 235 } 226 236 ··· 231 241 }, 232 242 ) 233 243 } 244 + 234 245 this.isOpening = true 235 246 dialog.show() 236 247 this.dialog = dialog 248 + 249 + ViewCompat.setOnApplyWindowInsetsListener(dialogRootViewGroup) { view, insets -> 250 + val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) 251 + val bottomSheet = dialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet) 252 + val behavior = bottomSheet?.let { BottomSheetBehavior.from(it) } 253 + 254 + val wasKeyboardVisible = isKeyboardVisible 255 + isKeyboardVisible = imeVisible 256 + 257 + if (imeVisible && behavior?.state == BottomSheetBehavior.STATE_HALF_EXPANDED) { 258 + behavior.state = BottomSheetBehavior.STATE_EXPANDED 259 + } else if (!imeVisible && wasKeyboardVisible) { 260 + updateLayout() 261 + } 262 + insets 263 + } 237 264 } 238 265 239 266 fun updateLayout() { ··· 246 273 val currentState = behavior.state 247 274 248 275 val oldRatio = behavior.halfExpandedRatio 249 - var newRatio = getHalfExpandedRatio(contentHeight) 276 + val newRatio = getHalfExpandedRatio(contentHeight) 250 277 behavior.halfExpandedRatio = newRatio 251 278 252 - if (contentHeight > this.safeScreenHeight && behavior.state != BottomSheetBehavior.STATE_EXPANDED) { 279 + if (preventExpansion) { 280 + behavior.maxHeight = (behavior.halfExpandedRatio * screenHeight).toInt() 281 + } 282 + 283 + val targetHeight = this.getTargetHeight() 284 + val availableHeight = screenHeight - getStatusBarHeight() - getNavigationBarHeight() 285 + val shouldBeExpanded = targetHeight >= availableHeight 286 + 287 + if (isKeyboardVisible) { 288 + if (behavior.state != BottomSheetBehavior.STATE_EXPANDED) { 289 + behavior.state = BottomSheetBehavior.STATE_EXPANDED 290 + } 291 + } else if (shouldBeExpanded && behavior.state != BottomSheetBehavior.STATE_EXPANDED && !preventExpansion) { 253 292 behavior.state = BottomSheetBehavior.STATE_EXPANDED 254 - } else if (contentHeight < this.safeScreenHeight && behavior.state != BottomSheetBehavior.STATE_HALF_EXPANDED) { 293 + } else if (!shouldBeExpanded && behavior.state != BottomSheetBehavior.STATE_HALF_EXPANDED) { 255 294 behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED 256 295 } else if (currentState == BottomSheetBehavior.STATE_HALF_EXPANDED && oldRatio != newRatio) { 257 296 behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED ··· 279 318 280 319 private fun getTargetHeight(): Float { 281 320 val contentHeight = this.getContentHeight() 282 - val height = 283 - if (contentHeight > maxHeight) { 284 - maxHeight 285 - } else if (contentHeight < minHeight) { 286 - minHeight 287 - } else { 288 - contentHeight 289 - } 290 - return height 321 + return when { 322 + contentHeight > maxHeight -> maxHeight 323 + contentHeight < minHeight -> minHeight 324 + else -> contentHeight 325 + } 291 326 } 292 327 293 - private fun clampRatio(ratio: Float): Float { 294 - if (ratio < 0.01) { 295 - return 0.01f 296 - } else if (ratio > 0.99) { 297 - return 0.99f 328 + private fun clampRatio(ratio: Float): Float = 329 + when { 330 + ratio < 0.01 -> 0.01f 331 + ratio > 0.99 -> 0.99f 332 + else -> ratio 298 333 } 299 - return ratio 300 - } 301 334 302 335 private fun setDraggable(draggable: Boolean) { 303 336 val dialog = this.dialog ?: return ··· 322 355 // View overrides to pass to DialogRootViewGroup instead 323 356 324 357 override fun dispatchProvideStructure(structure: ViewStructure?) { 325 - if (structure == null) { 326 - return 327 - } 358 + if (structure == null) return 328 359 dialogRootViewGroup.dispatchProvideStructure(structure) 329 360 } 330 361 ··· 363 394 // https://stackoverflow.com/questions/11862391/getheight-px-or-dpi 364 395 fun dpToPx(dp: Float): Float { 365 396 val displayMetrics = context.resources.displayMetrics 366 - val px = dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT) 367 - return px 397 + return dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT) 368 398 } 369 399 }
+2
modules/bottom-sheet/android/src/main/java/expo/modules/bottomsheet/DialogRootViewGroup.kt
··· 52 52 if (ReactFeatureFlags.dispatchPointerEvents) { 53 53 jSPointerDispatcher = JSPointerDispatcher(this) 54 54 } 55 + 56 + fitsSystemWindows = false 55 57 } 56 58 57 59 override fun onSizeChanged(
+20
modules/bottom-sheet/android/src/main/res/values/styles.xml
··· 1 + <?xml version="1.0" encoding="utf-8"?> 2 + <resources> 3 + <style name="EdgeToEdgeBottomSheetDialogTheme" parent="Theme.Material3.DayNight.BottomSheetDialog"> 4 + <!-- Enable edge-to-edge --> 5 + <item name="android:navigationBarColor">@android:color/transparent</item> 6 + <item name="android:statusBarColor">@android:color/transparent</item> 7 + <item name="android:windowIsFloating">false</item> 8 + <item name="enableEdgeToEdge">true</item> 9 + 10 + <!-- Configure bottom sheet to respect system window insets --> 11 + <item name="bottomSheetStyle">@style/EdgeToEdgeBottomSheet</item> 12 + </style> 13 + 14 + <style name="EdgeToEdgeBottomSheet" parent="Widget.Material3.BottomSheet"> 15 + <item name="paddingBottomSystemWindowInsets">false</item> 16 + <item name="paddingLeftSystemWindowInsets">true</item> 17 + <item name="paddingRightSystemWindowInsets">true</item> 18 + <item name="paddingTopSystemWindowInsets">false</item> 19 + </style> 20 + </resources>
+1
modules/bottom-sheet/src/BottomSheetNativeComponent.tsx
··· 175 175 Platform.OS === 'android' && { 176 176 borderTopLeftRadius: cornerRadius, 177 177 borderTopRightRadius: cornerRadius, 178 + overflow: 'hidden', 178 179 }, 179 180 extraStyles, 180 181 ]}>
+3
modules/expo-background-notification-handler/android/src/main/java/expo/modules/backgroundnotificationhandler/NotificationPrefs.kt
··· 34 34 is Boolean -> { 35 35 putBoolean(key, value) 36 36 } 37 + 37 38 is String -> { 38 39 putString(key, value) 39 40 } 41 + 40 42 is Array<*> -> { 41 43 putStringSet(key, value.map { it.toString() }.toSet()) 42 44 } 45 + 43 46 is Map<*, *> -> { 44 47 putStringSet(key, value.map { it.toString() }.toSet()) 45 48 }
+2 -2
modules/expo-receive-android-intents/android/src/main/java/xyz/blueskyweb/app/exporeceiveandroidintents/ExpoReceiveAndroidIntentsModule.kt
··· 117 117 118 118 private fun handleImageIntents( 119 119 uris: List<Uri>, 120 - text: String? 120 + text: String?, 121 121 ) { 122 122 var allParams = "" 123 123 ··· 145 145 146 146 private fun handleVideoIntents( 147 147 uris: List<Uri>, 148 - text: String? 148 + text: String?, 149 149 ) { 150 150 val uri = uris[0] 151 151 // If there is no extension for the file, substringAfterLast returns the original string - not
+1 -1
package.json
··· 202 202 "react-native-edge-to-edge": "^1.6.0", 203 203 "react-native-gesture-handler": "~2.28.0", 204 204 "react-native-get-random-values": "~1.11.0", 205 - "react-native-keyboard-controller": "1.18.5", 205 + "react-native-keyboard-controller": "^1.20.7", 206 206 "react-native-pager-view": "6.8.0", 207 207 "react-native-progress": "bluesky-social/react-native-progress", 208 208 "react-native-qrcode-styled": "^0.3.3",
+1 -1
src/App.native.tsx
··· 3 3 4 4 import React, {useEffect, useState} from 'react' 5 5 import {GestureHandlerRootView} from 'react-native-gesture-handler' 6 + import {KeyboardProvider as KeyboardControllerProvider} from 'react-native-keyboard-controller' 6 7 import { 7 8 initialWindowMetrics, 8 9 SafeAreaProvider, ··· 14 15 import {useLingui} from '@lingui/react' 15 16 import * as Sentry from '@sentry/react-native' 16 17 17 - import {KeyboardControllerProvider} from '#/lib/hooks/useEnableKeyboardController' 18 18 import {Provider as HideBottomBarBorderProvider} from '#/lib/hooks/useHideBottomBarBorder' 19 19 import {QueryProvider} from '#/lib/react-query' 20 20 import {s} from '#/lib/styles'
+3 -7
src/components/Dialog/index.tsx
··· 11 11 } from 'react-native' 12 12 import { 13 13 KeyboardAwareScrollView, 14 + type KeyboardAwareScrollViewRef, 14 15 useKeyboardHandler, 15 16 useReanimatedKeyboardAnimation, 16 17 } from 'react-native-keyboard-controller' ··· 23 24 import {msg} from '@lingui/macro' 24 25 import {useLingui} from '@lingui/react' 25 26 26 - import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' 27 27 import {ScrollProvider} from '#/lib/ScrollContext' 28 28 import {logger} from '#/logger' 29 29 import {useA11y} from '#/state/a11y' ··· 209 209 const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() 210 210 const insets = useSafeAreaInsets() 211 211 212 - useEnableKeyboardController(IS_IOS) 213 - 214 212 const [keyboardHeight, setKeyboardHeight] = React.useState(0) 215 213 214 + // note: iOS-only. keyboard-controller doesn't seem to work inside the sheets on Android 216 215 useKeyboardHandler( 217 216 { 218 217 onEnd: e => { ··· 231 230 } 232 231 paddingBottom = Math.max(paddingBottom, tokens.space._2xl) 233 232 } else { 234 - paddingBottom += keyboardHeight 235 233 if (nativeSnapPoint === BottomSheetSnapPoint.Full) { 236 234 paddingBottom += insets.top 237 235 } ··· 259 257 {paddingBottom}, 260 258 contentContainerStyle, 261 259 ]} 262 - ref={ref} 260 + ref={ref as React.Ref<KeyboardAwareScrollViewRef>} 263 261 showsVerticalScrollIndicator={IS_ANDROID ? false : undefined} 264 262 {...props} 265 263 bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} ··· 288 286 >(function InnerFlatList({footer, style, ...props}, ref) { 289 287 const insets = useSafeAreaInsets() 290 288 const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() 291 - 292 - useEnableKeyboardController(IS_IOS) 293 289 294 290 const onScroll = (e: ScrollEvent) => { 295 291 'worklet'
+1 -3
src/components/verification/VerifierDialog.tsx
··· 28 28 verificationState: FullVerificationState 29 29 }) { 30 30 return ( 31 - <Dialog.Outer control={control}> 31 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 32 32 <Dialog.Handle /> 33 33 <Inner 34 34 control={control} ··· 123 123 }), 124 124 )} 125 125 size="small" 126 - variant="solid" 127 126 color="primary" 128 127 style={[a.justify_center]} 129 128 onPress={() => { ··· 138 137 <Button 139 138 label={_(msg`Close dialog`)} 140 139 size="small" 141 - variant="solid" 142 140 color="secondary" 143 141 onPress={() => { 144 142 control.close()
-107
src/lib/hooks/useEnableKeyboardController.tsx
··· 1 - import { 2 - createContext, 3 - useCallback, 4 - useContext, 5 - useEffect, 6 - useMemo, 7 - useRef, 8 - } from 'react' 9 - import { 10 - KeyboardProvider, 11 - useKeyboardController, 12 - } from 'react-native-keyboard-controller' 13 - import {useFocusEffect} from '@react-navigation/native' 14 - 15 - const KeyboardControllerRefCountContext = createContext<{ 16 - incrementRefCount: () => void 17 - decrementRefCount: () => void 18 - }>({ 19 - incrementRefCount: () => {}, 20 - decrementRefCount: () => {}, 21 - }) 22 - KeyboardControllerRefCountContext.displayName = 23 - 'KeyboardControllerRefCountContext' 24 - 25 - export function KeyboardControllerProvider({ 26 - children, 27 - }: { 28 - children: React.ReactNode 29 - }) { 30 - return ( 31 - <KeyboardProvider enabled={false} preload={false}> 32 - <KeyboardControllerProviderInner> 33 - {children} 34 - </KeyboardControllerProviderInner> 35 - </KeyboardProvider> 36 - ) 37 - } 38 - 39 - function KeyboardControllerProviderInner({ 40 - children, 41 - }: { 42 - children: React.ReactNode 43 - }) { 44 - const {setEnabled} = useKeyboardController() 45 - const refCount = useRef(0) 46 - 47 - const value = useMemo( 48 - () => ({ 49 - incrementRefCount: () => { 50 - refCount.current++ 51 - setEnabled(refCount.current > 0) 52 - }, 53 - decrementRefCount: () => { 54 - refCount.current-- 55 - setEnabled(refCount.current > 0) 56 - 57 - if (__DEV__ && refCount.current < 0) { 58 - console.error('KeyboardController ref count < 0') 59 - } 60 - }, 61 - }), 62 - [setEnabled], 63 - ) 64 - 65 - return ( 66 - <KeyboardControllerRefCountContext.Provider value={value}> 67 - {children} 68 - </KeyboardControllerRefCountContext.Provider> 69 - ) 70 - } 71 - 72 - export function useEnableKeyboardController(shouldEnable: boolean) { 73 - const {incrementRefCount, decrementRefCount} = useContext( 74 - KeyboardControllerRefCountContext, 75 - ) 76 - 77 - useEffect(() => { 78 - if (!shouldEnable) { 79 - return 80 - } 81 - incrementRefCount() 82 - return () => { 83 - decrementRefCount() 84 - } 85 - }, [shouldEnable, incrementRefCount, decrementRefCount]) 86 - } 87 - 88 - /** 89 - * Like `useEnableKeyboardController`, but using `useFocusEffect` 90 - */ 91 - export function useEnableKeyboardControllerScreen(shouldEnable: boolean) { 92 - const {incrementRefCount, decrementRefCount} = useContext( 93 - KeyboardControllerRefCountContext, 94 - ) 95 - 96 - useFocusEffect( 97 - useCallback(() => { 98 - if (!shouldEnable) { 99 - return 100 - } 101 - incrementRefCount() 102 - return () => { 103 - decrementRefCount() 104 - } 105 - }, [shouldEnable, incrementRefCount, decrementRefCount]), 106 - ) 107 - }
-3
src/screens/FindContactsFlowScreen.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 import {usePreventRemove} from '@react-navigation/native' 6 6 7 - import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController' 8 7 import { 9 8 type AllNavigatorParams, 10 9 type NativeStackScreenProps, ··· 36 35 setTransitionDirection('Forward') 37 36 }) 38 37 }) 39 - 40 - useEnableKeyboardControllerScreen(true) 41 38 42 39 const setMinimalShellMode = useSetMinimalShellMode() 43 40 const effect = useCallback(() => {
-3
src/screens/Messages/Conversation.tsx
··· 15 15 } from '@react-navigation/native' 16 16 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 17 17 18 - import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController' 19 18 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 20 19 import { 21 20 type CommonNavigatorParams, ··· 67 66 68 67 const convoId = route.params.conversation 69 68 const {setCurrentConvoId} = useCurrentConvoId() 70 - 71 - useEnableKeyboardControllerScreen(true) 72 69 73 70 useFocusEffect( 74 71 useCallback(() => {
-3
src/screens/Onboarding/index.tsx
··· 2 2 import {View} from 'react-native' 3 3 import * as bcp47Match from 'bcp-47-match' 4 4 5 - import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController' 6 5 import {useLanguagePrefs} from '#/state/preferences' 7 6 import { 8 7 Layout, ··· 59 58 createInitialOnboardingState, 60 59 ) 61 60 const [contactsFlowState, contactsFlowDispatch] = useFindContactsFlowState() 62 - 63 - useEnableKeyboardControllerScreen(true) 64 61 65 62 return ( 66 63 <Portal>
-3
src/screens/StarterPack/Wizard/index.tsx
··· 16 16 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 17 17 18 18 import {STARTER_PACK_MAX_SIZE} from '#/lib/constants' 19 - import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController' 20 19 import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' 21 20 import { 22 21 type CommonNavigatorParams, ··· 184 183 gestureEnabled: false, 185 184 }) 186 185 }, [navigation]) 187 - 188 - useEnableKeyboardControllerScreen(true) 189 186 190 187 useFocusEffect( 191 188 React.useCallback(() => {
-3
src/screens/Takendown.tsx
··· 12 12 BLUESKY_MOD_SERVICE_HEADERS, 13 13 MAX_REPORT_REASON_GRAPHEME_LENGTH, 14 14 } from '#/lib/constants' 15 - import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' 16 15 import {cleanError} from '#/lib/strings/errors' 17 16 import {useAgent, useSession, useSessionApi} from '#/state/session' 18 17 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' ··· 120 119 ) 121 120 122 121 const webLayout = IS_WEB && gtMobile 123 - 124 - useEnableKeyboardController(true) 125 122 126 123 return ( 127 124 <View style={[a.util_screen_outer, a.flex_1]}>
+7 -2
src/view/com/composer/photos/ImageAltTextDialog.tsx
··· 5 5 import {useLingui} from '@lingui/react' 6 6 7 7 import {MAX_ALT_TEXT} from '#/lib/constants' 8 + import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible' 8 9 import {enforceLen} from '#/lib/strings/helpers' 9 10 import {type ComposerImage} from '#/state/gallery' 10 11 import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' ··· 28 29 image, 29 30 onChange, 30 31 }: Props): React.ReactNode => { 32 + const {height: minHeight} = useWindowDimensions() 31 33 const [altText, setAltText] = React.useState(image.alt) 32 34 33 35 return ( ··· 38 40 ...image, 39 41 alt: enforceLen(altText, MAX_ALT_TEXT, true), 40 42 }) 41 - }}> 43 + }} 44 + nativeOptions={{minHeight}}> 42 45 <Dialog.Handle /> 43 46 <ImageAltTextInner 44 47 control={control} ··· 64 67 const {_, i18n} = useLingui() 65 68 const t = useTheme() 66 69 const windim = useWindowDimensions() 70 + 71 + const [isKeyboardVisible] = useIsKeyboardVisible() 67 72 68 73 const imageStyle = React.useMemo<ImageStyle>(() => { 69 74 const maxWidth = IS_WEB ? 450 : windim.width ··· 165 170 </AltTextCounterWrapper> 166 171 </View> 167 172 {/* Maybe fix this later -h */} 168 - {IS_ANDROID ? <View style={{height: 300}} /> : null} 173 + {IS_ANDROID && isKeyboardVisible ? <View style={{height: 300}} /> : null} 169 174 </Dialog.ScrollableInner> 170 175 ) 171 176 }
+4 -4
yarn.lock
··· 17163 17163 resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz#64e10851abd9d176cbf2b40562f751622bde3358" 17164 17164 integrity sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q== 17165 17165 17166 - react-native-keyboard-controller@1.18.5: 17167 - version "1.18.5" 17168 - resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.18.5.tgz#ae12131f2019c574178479d2c55784f55e08bb68" 17169 - integrity sha512-wbYN6Tcu3G5a05dhRYBgjgd74KqoYWuUmroLpigRg9cXy5uYo7prTMIvMgvLtARQtUF7BOtFggUnzgoBOgk0TQ== 17166 + react-native-keyboard-controller@^1.20.7: 17167 + version "1.20.7" 17168 + resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.20.7.tgz#e1be1c15a5eb10b96a40a0812d8472e6e4bd8f29" 17169 + integrity sha512-G8S5jz1FufPrcL1vPtReATx+jJhT/j+sTqxMIb30b1z7cYEfMlkIzOCyaHgf6IMB2KA9uBmnA5M6ve2A9Ou4kw== 17170 17170 dependencies: 17171 17171 react-native-is-edge-to-edge "^1.2.1" 17172 17172