Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Blur effect on home screen (iOS 26) (#9935)

authored by

Samuel Newman and committed by
GitHub
65c4b783 9b24d75c

+49 -14
+22 -3
src/Navigation.tsx
··· 138 138 } from '#/components/dialogs/EmailDialog' 139 139 import {useAnalytics} from '#/analytics' 140 140 import {setNavigationMetadata} from '#/analytics/metadata' 141 - import {IS_NATIVE, IS_WEB} from '#/env' 141 + import {IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env' 142 142 import {router} from '#/routes' 143 143 import {Referrer} from '../modules/expo-bluesky-swiss-army' 144 144 ··· 685 685 function HomeTabNavigator() { 686 686 const t = useTheme() 687 687 688 + const BLURRED_SCROLL_EDGE_EFFECT = IS_LIQUID_GLASS 689 + ? ({ 690 + headerShown: true, 691 + headerTransparent: true, 692 + headerTitle: '', 693 + scrollEdgeEffects: { 694 + top: 'soft', 695 + }, 696 + } as const) 697 + : {} 698 + 688 699 return ( 689 700 <HomeTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Home"> 690 - <HomeTab.Screen name="Home" getComponent={() => HomeScreen} /> 691 - <HomeTab.Screen name="Start" getComponent={() => HomeScreen} /> 701 + <HomeTab.Screen 702 + name="Home" 703 + getComponent={() => HomeScreen} 704 + options={BLURRED_SCROLL_EDGE_EFFECT} 705 + /> 706 + <HomeTab.Screen 707 + name="Start" 708 + getComponent={() => HomeScreen} 709 + options={BLURRED_SCROLL_EDGE_EFFECT} 710 + /> 692 711 {commonScreens(HomeTab as typeof Flat)} 693 712 </HomeTab.Navigator> 694 713 )
+7 -4
src/components/hooks/useHeaderOffset.ts
··· 1 1 import {useWindowDimensions} from 'react-native' 2 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 2 3 3 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 4 + import {useBreakpoints} from '#/alf' 5 + import {IS_LIQUID_GLASS} from '#/env' 4 6 5 7 export function useHeaderOffset() { 6 - const {isDesktop, isTablet} = useWebMediaQueries() 8 + const {gtMobile} = useBreakpoints() 7 9 const {fontScale} = useWindowDimensions() 8 - if (isDesktop || isTablet) { 10 + const insets = useSafeAreaInsets() 11 + if (gtMobile) { 9 12 return 0 10 13 } 11 - const navBarHeight = 52 14 + const navBarHeight = 52 + (IS_LIQUID_GLASS ? insets.top : 0) 12 15 const tabBarPad = 10 + 10 + 3 // padding + border 13 16 const normalLineHeight = 20 // matches tab bar 14 17 const tabBarText = normalLineHeight * fontScale
+6 -1
src/lib/hooks/useMinimalShellTransform.ts
··· 1 1 import {interpolate, useAnimatedStyle} from 'react-native-reanimated' 2 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 2 3 3 4 import {useMinimalShellMode} from '#/state/shell/minimal-mode' 4 5 import {useShellLayout} from '#/state/shell/shell-layout' 6 + import {IS_LIQUID_GLASS} from '#/env' 5 7 6 8 // Keep these separated so that we only pay for useAnimatedStyle that gets used. 7 9 8 10 export function useMinimalShellHeaderTransform() { 9 11 const {headerMode} = useMinimalShellMode() 10 12 const {headerHeight} = useShellLayout() 13 + const {top: topInset} = useSafeAreaInsets() 14 + 15 + const headerPinnedHeight = IS_LIQUID_GLASS ? topInset : 0 11 16 12 17 const headerTransform = useAnimatedStyle(() => { 13 18 const headerModeValue = headerMode.get() ··· 19 24 translateY: interpolate( 20 25 headerModeValue, 21 26 [0, 1], 22 - [0, -headerHeight.get()], 27 + [0, headerPinnedHeight - headerHeight.get()], 23 28 ), 24 29 }, 25 30 ],
+5 -2
src/view/com/home/HomeHeaderLayoutMobile.tsx
··· 1 - import {type JSX} from 'react' 2 1 import {View} from 'react-native' 3 2 import Animated from 'react-native-reanimated' 3 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 4 import {msg} from '@lingui/core/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 17 17 import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag' 18 18 import * as Layout from '#/components/Layout' 19 19 import {Link} from '#/components/Link' 20 + import {IS_LIQUID_GLASS} from '#/env' 20 21 21 22 export function HomeHeaderLayoutMobile({ 22 23 children, 23 24 }: { 24 25 children: React.ReactNode 25 - tabBarAnchor: JSX.Element | null | undefined 26 + tabBarAnchor: React.ReactElement | null | undefined 26 27 }) { 27 28 const t = useTheme() 28 29 const {_} = useLingui() 29 30 const {headerHeight} = useShellLayout() 31 + const insets = useSafeAreaInsets() 30 32 const headerMinimalShellTransform = useMinimalShellHeaderTransform() 31 33 const {hasSession} = useSession() 32 34 const playHaptic = useHaptics() ··· 42 44 left: 0, 43 45 right: 0, 44 46 }, 47 + IS_LIQUID_GLASS && {paddingTop: insets.top}, 45 48 headerMinimalShellTransform, 46 49 ]} 47 50 onLayout={e => {
+7 -2
src/view/com/util/MainScrollProvider.tsx
··· 6 6 useSharedValue, 7 7 withSpring, 8 8 } from 'react-native-reanimated' 9 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 9 10 import EventEmitter from 'eventemitter3' 10 11 11 12 import {ScrollProvider} from '#/lib/ScrollContext' 12 13 import {useMinimalShellMode} from '#/state/shell' 13 14 import {useShellLayout} from '#/state/shell/shell-layout' 14 - import {IS_NATIVE, IS_WEB} from '#/env' 15 + import {IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env' 15 16 16 17 const WEB_HIDE_SHELL_THRESHOLD = 200 17 18 18 19 export function MainScrollProvider({children}: {children: React.ReactNode}) { 19 20 const {headerHeight} = useShellLayout() 20 21 const {headerMode} = useMinimalShellMode() 22 + const {top: topInset} = useSafeAreaInsets() 23 + const headerPinnedHeight = IS_LIQUID_GLASS ? topInset : 0 21 24 const startDragOffset = useSharedValue<number | null>(null) 22 25 const startMode = useSharedValue<number | null>(null) 23 26 const didJustRestoreScroll = useSharedValue<boolean>(false) ··· 126 129 // The "mode" value is always between 0 and 1. 127 130 // Figure out how much to move it based on the current dragged distance. 128 131 const dy = offsetY - startDragOffsetValue 132 + const hideDistance = headerHeight.get() - headerPinnedHeight 129 133 const dProgress = interpolate( 130 134 dy, 131 - [-headerHeight.get(), headerHeight.get()], 135 + [-hideDistance, hideDistance], 132 136 [-1, 1], 133 137 ) 134 138 const newValue = clamp(startModeValue + dProgress, 0, 1) ··· 156 160 }, 157 161 [ 158 162 headerHeight, 163 + headerPinnedHeight, 159 164 headerMode, 160 165 setMode, 161 166 startDragOffset,
+2 -2
src/view/screens/Home.tsx
··· 36 36 import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned' 37 37 import * as Layout from '#/components/Layout' 38 38 import {useAnalytics} from '#/analytics' 39 - import {IS_WEB} from '#/env' 39 + import {IS_LIQUID_GLASS, IS_WEB} from '#/env' 40 40 import {useDemoMode} from '#/storage/hooks/demo-mode' 41 41 42 42 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'> ··· 79 79 80 80 if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) { 81 81 return ( 82 - <Layout.Screen testID="HomeScreen"> 82 + <Layout.Screen testID="HomeScreen" noInsetTop={IS_LIQUID_GLASS}> 83 83 <HomeScreenReady 84 84 {...props} 85 85 preferences={preferences}