Bluesky app fork with some witchin' additions 💫

Merge branch 'main' into d/profile-screen

DS Boyce 4356ff00 54598571

+299 -324
+6 -1
.github/workflows/build-submit-ios.yml
··· 118 118 fi 119 119 120 120 - name: 🚀 Deploy 121 - run: eas submit -p ios --non-interactive --path ios-build/ios/build/Bluesky.ipa 121 + env: 122 + PROFILE: ${{ inputs.profile || 'testflight' }} 123 + run: > 124 + eas submit -p ios --non-interactive 125 + --path ios-build/ios/build/Bluesky.ipa 126 + --what-to-test "$([[ "$PROFILE" == "production" ]] && echo "Production build" || echo "TestFlight build")" 122 127 123 128 - name: 🪲 Upload dSYM to Sentry 124 129 run: >
+1 -1
.github/workflows/bundle-deploy-eas-update.yml
··· 242 242 --local --output build.ipa --non-interactive 243 243 244 244 - name: 🚀 Deploy 245 - run: eas submit -p ios --non-interactive --path build.ipa 245 + run: eas submit -p ios --non-interactive --path build.ipa --what-to-test "TestFlight build" 246 246 247 247 - name: ⬇️ Restore Cache 248 248 id: get-base-commit
+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'
+4 -2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx
··· 58 58 <BlueskyVideoView 59 59 url={embed.playlist} 60 60 autoplay={!autoplayDisabled && !isWithinMessage} 61 - beginMuted={isGif || autoplayDisabled ? false : muted} 61 + beginMuted={isGif || (autoplayDisabled ? false : muted)} 62 62 style={[a.rounded_sm]} 63 63 onActiveChange={e => { 64 64 setIsActive(e.nativeEvent.isActive) ··· 67 67 setIsLoading(e.nativeEvent.isLoading) 68 68 }} 69 69 onMutedChange={e => { 70 - setMuted(e.nativeEvent.isMuted) 70 + if (!isGif) { 71 + setMuted(e.nativeEvent.isMuted) 72 + } 71 73 }} 72 74 onStatusChange={e => { 73 75 setStatus(e.nativeEvent.status)
+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 - }
+64 -64
src/locale/locales/en/messages.po
··· 129 129 130 130 #. Number of users (always at least 25) who have joined Bluesky using a specific starter pack 131 131 #: src/screens/StarterPack/StarterPackScreen.tsx:499 132 - msgid "{0, plural, other {# people have}} used this starter pack!" 132 + msgid "{0, plural, other {# people have}} joined Bluesky via this starter pack!" 133 133 msgstr "" 134 134 135 135 #: src/components/dialogs/StarterPackDialog.tsx:361 ··· 242 242 msgstr "" 243 243 244 244 #: src/lib/generate-starterpack.ts:104 245 - #: src/screens/StarterPack/Wizard/index.tsx:202 245 + #: src/screens/StarterPack/Wizard/index.tsx:199 246 246 msgid "{displayName}'s Starter Pack" 247 247 msgstr "" 248 248 ··· 475 475 msgid "+{computedTotal}" 476 476 msgstr "" 477 477 478 - #: src/screens/StarterPack/Wizard/index.tsx:525 478 + #: src/screens/StarterPack/Wizard/index.tsx:522 479 479 msgctxt "profiles" 480 480 msgid "<0>{0}, </0><1>{1}, </1>and {2, plural, one {# other} other {# others}} are included in your starter pack" 481 481 msgstr "" 482 482 483 - #: src/screens/StarterPack/Wizard/index.tsx:578 483 + #: src/screens/StarterPack/Wizard/index.tsx:575 484 484 msgctxt "feeds" 485 485 msgid "<0>{0}, </0><1>{1}, </1>and {2, plural, one {# other} other {# others}} are included in your starter pack" 486 486 msgstr "" ··· 493 493 msgid "<0>{0}</0> {1, plural, one {following} other {following}}" 494 494 msgstr "" 495 495 496 - #: src/screens/StarterPack/Wizard/index.tsx:512 497 - #: src/screens/StarterPack/Wizard/index.tsx:566 496 + #: src/screens/StarterPack/Wizard/index.tsx:509 497 + #: src/screens/StarterPack/Wizard/index.tsx:563 498 498 msgid "<0>{0}</0> and<1> </1><2>{1} </2>are included in your starter pack" 499 499 msgstr "" 500 500 501 - #: src/screens/StarterPack/Wizard/index.tsx:559 501 + #: src/screens/StarterPack/Wizard/index.tsx:556 502 502 msgid "<0>{0}</0> is included in your starter pack" 503 503 msgstr "" 504 504 ··· 515 515 msgid "<0>Sign in</0><1> or </1><2>create an account</2><3> </3><4>to search for news, sports, politics, and everything else happening on Bluesky.</4>" 516 516 msgstr "" 517 517 518 - #: src/screens/StarterPack/Wizard/index.tsx:503 518 + #: src/screens/StarterPack/Wizard/index.tsx:500 519 519 msgid "<0>You</0> and<1> </1><2>{0} </2>are included in your starter pack" 520 520 msgstr "" 521 521 ··· 581 581 582 582 #. Contains a post that originally appeared in English. Consider translating the post text if it makes sense in your language, and noting that the post was translated from English. 583 583 #: src/components/dialogs/nuxs/DraftsAnnouncement.tsx:97 584 - msgid "A screenshot of a the post composer with a new button next to the post button that says \"Drafts\", with a rainbow firework effect. Below, the text in the composer reads \"Hey, did you hear the news? Bluesky has drafts now???\"." 584 + msgid "A screenshot of the post composer with a new button next to the post button that says \"Drafts\", with a rainbow firework effect. Below, the text in the composer reads \"Hey, did you hear the news? Bluesky has drafts now!!!\"." 585 585 msgstr "" 586 586 587 587 #: src/Navigation.tsx:535 ··· 710 710 msgid "Add" 711 711 msgstr "" 712 712 713 - #: src/screens/StarterPack/Wizard/index.tsx:614 713 + #: src/screens/StarterPack/Wizard/index.tsx:611 714 714 msgid "Add {0} more to continue" 715 715 msgstr "" 716 716 ··· 742 742 #: src/view/com/composer/GifAltText.tsx:211 743 743 #: src/view/com/composer/photos/Gallery.tsx:170 744 744 #: src/view/com/composer/photos/Gallery.tsx:217 745 - #: src/view/com/composer/photos/ImageAltTextDialog.tsx:88 746 745 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:93 746 + #: src/view/com/composer/photos/ImageAltTextDialog.tsx:98 747 747 msgid "Add alt text" 748 748 msgstr "" 749 749 ··· 819 819 msgid "Add recommended feeds" 820 820 msgstr "" 821 821 822 - #: src/screens/StarterPack/Wizard/index.tsx:547 822 + #: src/screens/StarterPack/Wizard/index.tsx:544 823 823 msgid "Add some feeds to your starter pack!" 824 824 msgstr "" 825 825 ··· 1029 1029 1030 1030 #: src/screens/Settings/AccessibilitySettings.tsx:54 1031 1031 #: src/view/com/composer/GifAltText.tsx:154 1032 - #: src/view/com/composer/photos/ImageAltTextDialog.tsx:117 1032 + #: src/view/com/composer/photos/ImageAltTextDialog.tsx:122 1033 1033 #: src/view/com/composer/videos/SubtitleDialog.tsx:40 1034 1034 #: src/view/com/composer/videos/SubtitleDialog.tsx:58 1035 1035 #: src/view/com/composer/videos/SubtitleDialog.tsx:109 ··· 1050 1050 msgstr "" 1051 1051 1052 1052 #: src/view/com/composer/GifAltText.tsx:179 1053 - #: src/view/com/composer/photos/ImageAltTextDialog.tsx:138 1053 + #: src/view/com/composer/photos/ImageAltTextDialog.tsx:143 1054 1054 msgid "Alt text will be truncated. {MAX_ALT_TEXT, plural, other {Limit: {0} characters.}}" 1055 1055 msgstr "" 1056 1056 ··· 1277 1277 msgid "Appeal submitted" 1278 1278 msgstr "" 1279 1279 1280 - #: src/screens/Takendown.tsx:114 1281 - #: src/screens/Takendown.tsx:142 1280 + #: src/screens/Takendown.tsx:113 1281 + #: src/screens/Takendown.tsx:139 1282 1282 msgid "Appeal suspension" 1283 1283 msgstr "" 1284 1284 1285 - #: src/screens/Takendown.tsx:117 1285 + #: src/screens/Takendown.tsx:116 1286 1286 msgid "Appeal Suspension" 1287 1287 msgstr "" 1288 1288 ··· 1408 1408 #: src/screens/Settings/components/ChangePasswordDialog.tsx:272 1409 1409 #: src/screens/Settings/components/ChangePasswordDialog.tsx:281 1410 1410 #: src/screens/Signup/BackNextButtons.tsx:41 1411 - #: src/screens/StarterPack/Wizard/index.tsx:324 1411 + #: src/screens/StarterPack/Wizard/index.tsx:321 1412 1412 #: src/view/com/composer/drafts/DraftsListDialog.tsx:80 1413 1413 #: src/view/com/composer/drafts/DraftsListDialog.tsx:86 1414 1414 msgid "Back" ··· 1453 1453 #: src/components/dms/dialogs/NewChatDialog.tsx:55 1454 1454 #: src/components/dms/MessageProfileButton.tsx:59 1455 1455 #: src/screens/Messages/ChatList.tsx:371 1456 - #: src/screens/Messages/Conversation.tsx:228 1456 + #: src/screens/Messages/Conversation.tsx:225 1457 1457 msgid "Before you can message another user, you must first verify your email." 1458 1458 msgstr "" 1459 1459 ··· 1617 1617 msgid "Bluesky is more fun with friends. Do you want to invite some of yours? <0/>" 1618 1618 msgstr "" 1619 1619 1620 - #: src/screens/Takendown.tsx:215 1620 + #: src/screens/Takendown.tsx:212 1621 1621 msgid "Bluesky Social Terms of Service" 1622 1622 msgstr "" 1623 1623 ··· 1788 1788 #: src/screens/Settings/components/ChangePasswordDialog.tsx:247 1789 1789 #: src/screens/Settings/components/ChangePasswordDialog.tsx:253 1790 1790 #: src/screens/Settings/Settings.tsx:305 1791 - #: src/screens/Takendown.tsx:102 1792 - #: src/screens/Takendown.tsx:105 1791 + #: src/screens/Takendown.tsx:101 1792 + #: src/screens/Takendown.tsx:104 1793 1793 #: src/view/com/composer/Composer.tsx:1449 1794 1794 #: src/view/com/composer/Composer.tsx:1461 1795 1795 #: src/view/com/composer/photos/EditImageDialog.web.tsx:43 ··· 1987 1987 msgid "Choose domain verification method" 1988 1988 msgstr "" 1989 1989 1990 - #: src/screens/StarterPack/Wizard/index.tsx:218 1990 + #: src/screens/StarterPack/Wizard/index.tsx:215 1991 1991 msgid "Choose Feeds" 1992 1992 msgstr "" 1993 1993 ··· 1995 1995 msgid "Choose for me" 1996 1996 msgstr "" 1997 1997 1998 - #: src/screens/StarterPack/Wizard/index.tsx:214 1998 + #: src/screens/StarterPack/Wizard/index.tsx:211 1999 1999 msgid "Choose People" 2000 2000 msgstr "" 2001 2001 ··· 2119 2119 #: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:118 2120 2120 #: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:124 2121 2121 #: src/components/verification/VerificationsDialog.tsx:145 2122 - #: src/components/verification/VerifierDialog.tsx:147 2122 + #: src/components/verification/VerifierDialog.tsx:145 2123 2123 #: src/components/WhoCanReply.tsx:235 2124 2124 #: src/components/WhoCanReply.tsx:242 2125 2125 #: src/screens/Settings/components/ChangePasswordDialog.tsx:287 ··· 2147 2147 #: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:228 2148 2148 #: src/components/dialogs/GifSelect.tsx:263 2149 2149 #: src/components/verification/VerificationsDialog.tsx:137 2150 - #: src/components/verification/VerifierDialog.tsx:139 2150 + #: src/components/verification/VerifierDialog.tsx:138 2151 2151 #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:204 2152 2152 #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:298 2153 2153 #: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:330 ··· 2433 2433 msgid "Continue to next step" 2434 2434 msgstr "" 2435 2435 2436 - #: src/screens/Messages/Conversation.tsx:57 2436 + #: src/screens/Messages/Conversation.tsx:56 2437 2437 msgid "Conversation" 2438 2438 msgstr "" 2439 2439 ··· 2926 2926 msgstr "" 2927 2927 2928 2928 #: src/view/com/composer/GifAltText.tsx:150 2929 - #: src/view/com/composer/photos/ImageAltTextDialog.tsx:113 2929 + #: src/view/com/composer/photos/ImageAltTextDialog.tsx:118 2930 2930 msgid "Descriptive alt text" 2931 2931 msgstr "" 2932 2932 ··· 3040 3040 msgid "Discover New Feeds" 3041 3041 msgstr "" 3042 3042 3043 - #: src/components/Dialog/index.tsx:379 3043 + #: src/components/Dialog/index.tsx:375 3044 3044 msgid "Dismiss" 3045 3045 msgstr "" 3046 3046 ··· 3159 3159 msgid "Double tap or long press the message to add a reaction" 3160 3160 msgstr "" 3161 3161 3162 - #: src/components/Dialog/index.tsx:380 3162 + #: src/components/Dialog/index.tsx:376 3163 3163 msgid "Double tap to close the dialog" 3164 3164 msgstr "" 3165 3165 ··· 3238 3238 #: src/screens/Settings/AccountSettings.tsx:145 3239 3239 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:252 3240 3240 #: src/screens/StarterPack/StarterPackScreen.tsx:598 3241 - #: src/screens/StarterPack/Wizard/index.tsx:340 3242 - #: src/screens/StarterPack/Wizard/index.tsx:345 3241 + #: src/screens/StarterPack/Wizard/index.tsx:337 3242 + #: src/screens/StarterPack/Wizard/index.tsx:342 3243 3243 msgid "Edit" 3244 3244 msgstr "" 3245 3245 ··· 3726 3726 msgid "Failed to create conversation" 3727 3727 msgstr "" 3728 3728 3729 - #: src/screens/StarterPack/Wizard/index.tsx:263 3730 - #: src/screens/StarterPack/Wizard/index.tsx:271 3729 + #: src/screens/StarterPack/Wizard/index.tsx:260 3730 + #: src/screens/StarterPack/Wizard/index.tsx:268 3731 3731 msgid "Failed to create starter pack" 3732 3732 msgstr "" 3733 3733 ··· 4098 4098 msgid "Finding friends..." 4099 4099 msgstr "" 4100 4100 4101 - #: src/screens/StarterPack/Wizard/index.tsx:219 4101 + #: src/screens/StarterPack/Wizard/index.tsx:216 4102 4102 msgid "Finish" 4103 4103 msgstr "" 4104 4104 ··· 5118 5118 msgid "It's correct" 5119 5119 msgstr "" 5120 5120 5121 - #: src/screens/StarterPack/Wizard/index.tsx:492 5121 + #: src/screens/StarterPack/Wizard/index.tsx:489 5122 5122 msgid "It's just <0>{0} </0>right now! Add more people to your starter pack by searching above." 5123 5123 msgstr "" 5124 5124 5125 - #: src/screens/StarterPack/Wizard/index.tsx:487 5125 + #: src/screens/StarterPack/Wizard/index.tsx:484 5126 5126 msgid "It's just you right now! Add more people to your starter pack by searching above." 5127 5127 msgstr "" 5128 5128 ··· 5219 5219 msgstr "" 5220 5220 5221 5221 #: src/components/verification/VerificationsDialog.tsx:167 5222 - #: src/components/verification/VerifierDialog.tsx:135 5222 + #: src/components/verification/VerifierDialog.tsx:134 5223 5223 #: src/screens/Moderation/VerificationSettings.tsx:49 5224 5224 #: src/screens/Profile/Header/EditProfileDialog.tsx:349 5225 5225 #: src/screens/Settings/components/ChangeHandleDialog.tsx:213 ··· 6140 6140 #: src/screens/Settings/components/AddAppPasswordDialog.tsx:157 6141 6141 #: src/screens/Settings/components/AddAppPasswordDialog.tsx:165 6142 6142 #: src/screens/Signup/BackNextButtons.tsx:67 6143 - #: src/screens/StarterPack/Wizard/index.tsx:211 6144 - #: src/screens/StarterPack/Wizard/index.tsx:215 6145 - #: src/screens/StarterPack/Wizard/index.tsx:393 6146 - #: src/screens/StarterPack/Wizard/index.tsx:400 6143 + #: src/screens/StarterPack/Wizard/index.tsx:208 6144 + #: src/screens/StarterPack/Wizard/index.tsx:212 6145 + #: src/screens/StarterPack/Wizard/index.tsx:390 6146 + #: src/screens/StarterPack/Wizard/index.tsx:397 6147 6147 msgid "Next" 6148 6148 msgstr "" 6149 6149 ··· 6355 6355 msgid "None" 6356 6356 msgstr "" 6357 6357 6358 - #: src/screens/FindContactsFlowScreen.tsx:70 6358 + #: src/screens/FindContactsFlowScreen.tsx:67 6359 6359 #: src/screens/Settings/FindContactsSettings.tsx:103 6360 6360 msgid "Not available on this platform." 6361 6361 msgstr "" ··· 7138 7138 msgid "Please use the native app to import your contacts." 7139 7139 msgstr "" 7140 7140 7141 - #: src/screens/FindContactsFlowScreen.tsx:71 7141 + #: src/screens/FindContactsFlowScreen.tsx:68 7142 7142 msgid "Please use the native app to sync your contacts." 7143 7143 msgstr "" 7144 7144 ··· 7556 7556 msgid "Real people." 7557 7557 msgstr "" 7558 7558 7559 - #: src/screens/Takendown.tsx:160 7560 - #: src/screens/Takendown.tsx:168 7559 + #: src/screens/Takendown.tsx:157 7560 + #: src/screens/Takendown.tsx:165 7561 7561 msgid "Reason for appeal" 7562 7562 msgstr "" 7563 7563 ··· 8132 8132 msgid "Returns to previous page" 8133 8133 msgstr "" 8134 8134 8135 - #: src/screens/StarterPack/Wizard/index.tsx:325 8135 + #: src/screens/StarterPack/Wizard/index.tsx:322 8136 8136 msgid "Returns to the previous step" 8137 8137 msgstr "" 8138 8138 ··· 8152 8152 #: src/view/com/composer/GifAltText.tsx:202 8153 8153 #: src/view/com/composer/photos/EditImageDialog.web.tsx:62 8154 8154 #: src/view/com/composer/photos/EditImageDialog.web.tsx:75 8155 - #: src/view/com/composer/photos/ImageAltTextDialog.tsx:152 8156 - #: src/view/com/composer/photos/ImageAltTextDialog.tsx:162 8155 + #: src/view/com/composer/photos/ImageAltTextDialog.tsx:157 8156 + #: src/view/com/composer/photos/ImageAltTextDialog.tsx:167 8157 8157 msgid "Save" 8158 8158 msgstr "" 8159 8159 ··· 8305 8305 msgid "Search for \"{searchText}\"" 8306 8306 msgstr "" 8307 8307 8308 - #: src/screens/StarterPack/Wizard/index.tsx:550 8308 + #: src/screens/StarterPack/Wizard/index.tsx:547 8309 8309 msgid "Search for feeds that you want to suggest to others." 8310 8310 msgstr "" 8311 8311 ··· 9001 9001 #: src/screens/Settings/Settings.tsx:304 9002 9002 #: src/screens/SignupQueued.tsx:93 9003 9003 #: src/screens/SignupQueued.tsx:96 9004 - #: src/screens/Takendown.tsx:88 9004 + #: src/screens/Takendown.tsx:87 9005 9005 #: src/view/shell/desktop/LeftNav.tsx:212 9006 9006 #: src/view/shell/desktop/LeftNav.tsx:269 9007 9007 #: src/view/shell/desktop/LeftNav.tsx:272 9008 9008 msgid "Sign out" 9009 9009 msgstr "" 9010 9010 9011 - #: src/screens/Takendown.tsx:91 9011 + #: src/screens/Takendown.tsx:90 9012 9012 msgid "Sign Out" 9013 9013 msgstr "" 9014 9014 ··· 9035 9035 #: src/screens/Onboarding/StepFinished/index.tsx:316 9036 9036 #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:261 9037 9037 #: src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx:104 9038 - #: src/screens/StarterPack/Wizard/index.tsx:219 9038 + #: src/screens/StarterPack/Wizard/index.tsx:216 9039 9039 msgid "Skip" 9040 9040 msgstr "" 9041 9041 ··· 9098 9098 msgid "Something wasn't quite right with the data you're trying to report. Please contact support." 9099 9099 msgstr "" 9100 9100 9101 - #: src/screens/Messages/Conversation.tsx:144 9101 + #: src/screens/Messages/Conversation.tsx:141 9102 9102 msgid "Something went wrong" 9103 9103 msgstr "" 9104 9104 ··· 9194 9194 9195 9195 #: src/Navigation.tsx:590 9196 9196 #: src/Navigation.tsx:595 9197 - #: src/screens/StarterPack/Wizard/index.tsx:210 9197 + #: src/screens/StarterPack/Wizard/index.tsx:207 9198 9198 msgid "Starter Pack" 9199 9199 msgstr "" 9200 9200 ··· 9270 9270 msgid "Submit" 9271 9271 msgstr "" 9272 9272 9273 - #: src/screens/Takendown.tsx:76 9273 + #: src/screens/Takendown.tsx:75 9274 9274 msgid "Submit appeal" 9275 9275 msgstr "" 9276 9276 9277 - #: src/screens/Takendown.tsx:80 9277 + #: src/screens/Takendown.tsx:79 9278 9278 msgid "Submit Appeal" 9279 9279 msgstr "" 9280 9280 ··· 9505 9505 #: src/screens/StarterPack/StarterPackScreen.tsx:113 9506 9506 #: src/screens/StarterPack/StarterPackScreen.tsx:157 9507 9507 #: src/screens/StarterPack/StarterPackScreen.tsx:158 9508 - #: src/screens/StarterPack/Wizard/index.tsx:117 9509 - #: src/screens/StarterPack/Wizard/index.tsx:127 9508 + #: src/screens/StarterPack/Wizard/index.tsx:116 9509 + #: src/screens/StarterPack/Wizard/index.tsx:126 9510 9510 msgid "That starter pack could not be found." 9511 9511 msgstr "" 9512 9512 ··· 10970 10970 msgid "We couldn't find any results for that topic." 10971 10971 msgstr "" 10972 10972 10973 - #: src/screens/Messages/Conversation.tsx:145 10973 + #: src/screens/Messages/Conversation.tsx:142 10974 10974 msgid "We couldn't load this conversation" 10975 10975 msgstr "" 10976 10976 ··· 11191 11191 msgid "Whoops! Trending videos failed to load." 11192 11192 msgstr "" 11193 11193 11194 - #: src/screens/Takendown.tsx:171 11194 + #: src/screens/Takendown.tsx:168 11195 11195 msgid "Why are you appealing?" 11196 11196 msgstr "" 11197 11197 ··· 11751 11751 msgid "Your account has been deleted" 11752 11752 msgstr "" 11753 11753 11754 - #: src/screens/Takendown.tsx:144 11754 + #: src/screens/Takendown.tsx:141 11755 11755 msgid "Your account has been suspended" 11756 11756 msgstr "" 11757 11757 ··· 11763 11763 msgid "Your account repository, containing all public data records, can be downloaded as a \"CAR\" file. This file does not include media embeds, such as images, or your private data, which must be fetched separately." 11764 11764 msgstr "" 11765 11765 11766 - #: src/screens/Takendown.tsx:212 11766 + #: src/screens/Takendown.tsx:209 11767 11767 msgid "Your account was found to be in violation of the <0>Bluesky Social Terms of Service</0>. You have been sent an email outlining the specific violation and suspension period, if applicable. You can appeal this decision if you believe it was made in error." 11768 11768 msgstr "" 11769 11769 11770 - #: src/screens/Takendown.tsx:152 11770 + #: src/screens/Takendown.tsx:149 11771 11771 msgid "Your appeal has been submitted. If your appeal succeeds, you will receive an email." 11772 11772 msgstr "" 11773 11773
-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>
+24 -9
src/screens/ProfileList/index.tsx
··· 1 - import {useCallback, useMemo, useRef} from 'react' 1 + import {useCallback, useMemo, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {useAnimatedRef} from 'react-native-reanimated' 4 4 import { ··· 35 35 import {FAB} from '#/view/com/util/fab/FAB' 36 36 import {type ListRef} from '#/view/com/util/List' 37 37 import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' 38 - import {atoms as a, platform} from '#/alf' 38 + import {atoms as a, native, platform, useTheme} from '#/alf' 39 39 import {useDialogControl} from '#/components/Dialog' 40 40 import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog' 41 41 import * as Layout from '#/components/Layout' 42 42 import {Loader} from '#/components/Loader' 43 43 import * as Hider from '#/components/moderation/Hider' 44 + import {IS_WEB} from '#/env' 44 45 import {AboutSection} from './AboutSection' 45 46 import {ErrorScreen} from './components/ErrorScreen' 46 47 import {Header} from './components/Header' ··· 149 150 moderationOpts: ModerationOpts 150 151 preferences: UsePreferencesQueryResponse 151 152 }) { 153 + const t = useTheme() 152 154 const {_} = useLingui() 153 155 const queryClient = useQueryClient() 154 156 const {openComposer} = useOpenComposer() ··· 164 166 const scrollElRef = useAnimatedRef() 165 167 const addUserDialogControl = useDialogControl() 166 168 const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)] 169 + // modlist only 170 + const [headerHeight, setHeaderHeight] = useState<number | null>(null) 167 171 168 172 const moderation = useMemo(() => { 169 173 return moderateUserList(list, moderationOpts) ··· 263 267 </Hider.Mask> 264 268 <Hider.Content> 265 269 <View style={[a.util_screen_outer]}> 266 - <Layout.Center>{renderHeader()}</Layout.Center> 267 - <AboutSection 268 - list={list} 269 - scrollElRef={scrollElRef as ListRef} 270 - onPressAddUser={addUserDialogControl.open} 271 - headerHeight={0} 272 - /> 270 + <Layout.Center 271 + onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)} 272 + style={[ 273 + native([a.absolute, a.z_10, t.atoms.bg]), 274 + 275 + a.border_b, 276 + t.atoms.border_contrast_low, 277 + ]}> 278 + {renderHeader()} 279 + </Layout.Center> 280 + {headerHeight !== null && ( 281 + <AboutSection 282 + list={list} 283 + scrollElRef={scrollElRef as ListRef} 284 + onPressAddUser={addUserDialogControl.open} 285 + headerHeight={IS_WEB ? 0 : headerHeight} 286 + /> 287 + )} 273 288 <FAB 274 289 testID="composeFAB" 275 290 onPress={() => openComposer({logContext: 'Fab'})}
-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]}>
+9 -1
src/state/queries/preferences/index.ts
··· 200 200 }) 201 201 } 202 202 203 - export function useSetThreadViewPreferencesMutation() { 203 + export function useSetThreadViewPreferencesMutation({ 204 + onSuccess, 205 + onError, 206 + }: { 207 + onSuccess?: (data: void, variables: Partial<ThreadViewPreferences>) => void 208 + onError?: (error: unknown) => void 209 + }) { 204 210 const queryClient = useQueryClient() 205 211 const agent = useAgent() 206 212 ··· 212 218 queryKey: preferencesQueryKey, 213 219 }) 214 220 }, 221 + onSuccess, 222 + onError, 215 223 }) 216 224 } 217 225
+30 -18
src/state/queries/preferences/useThreadPreferences.ts
··· 1 1 import {useCallback, useMemo, useRef, useState} from 'react' 2 2 import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api' 3 + import {useFocusEffect} from '@react-navigation/native' 3 4 import debounce from 'lodash.debounce' 4 5 5 6 import {useCallOnce} from '#/lib/once' ··· 70 71 } 71 72 72 73 const userUpdatedPrefs = useRef(false) 73 - const [isSaving, setIsSaving] = useState(false) 74 - const {mutateAsync} = useSetThreadViewPreferencesMutation() 74 + const {mutate, isPending: isSaving} = useSetThreadViewPreferencesMutation({ 75 + onSuccess: (_data, prefs) => { 76 + ax.metric('thread:preferences:update', { 77 + sort: prefs.sort, 78 + view: prefs.lab_treeViewEnabled ? 'tree' : 'linear', 79 + }) 80 + }, 81 + onError: err => { 82 + ax.logger.error('useThreadPreferences failed to save', { 83 + safeMessage: err, 84 + }) 85 + }, 86 + }) 75 87 const savePrefs = useMemo(() => { 76 - return debounce(async (prefs: ThreadViewPreferences) => { 77 - try { 78 - setIsSaving(true) 79 - await mutateAsync(prefs) 80 - ax.metric('thread:preferences:update', { 81 - sort: prefs.sort, 82 - view: prefs.lab_treeViewEnabled ? 'tree' : 'linear', 83 - }) 84 - } catch (e) { 85 - ax.logger.error('useThreadPreferences failed to save', { 86 - safeMessage: e, 87 - }) 88 - } finally { 89 - setIsSaving(false) 88 + return debounce( 89 + (prefs: ThreadViewPreferences) => { 90 + mutate(prefs) 91 + }, 92 + 2e3, 93 + {leading: true, trailing: true}, 94 + ) 95 + }, [mutate]) 96 + 97 + // flush on leave screen 98 + useFocusEffect( 99 + useCallback(() => { 100 + return () => { 101 + void savePrefs.flush() 90 102 } 91 - }, 4e3) 92 - }, [mutateAsync]) 103 + }, [savePrefs]), 104 + ) 93 105 94 106 if (save && userUpdatedPrefs.current) { 95 107 savePrefs({
+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