Bluesky app fork with some witchin' additions 💫

[Layout] Bleed profile banner into safe area (#6967)

* bleed profile banner into safe area

(cherry picked from commit 50b3a4d0c6fd94b583ffe4efa65de35c81ae7f4e)

* pointer events none when hidden

(cherry picked from commit bae2c7b2dd6d7f858a98812196628308c0877755)

* fix web

(cherry picked from commit e3f9597170375f2903b6e567b963f008ec95aed1)

* add status bar shadow

* rm log

* rm mini header

* speed up animation

* pass bool rather than int in light status bar

authored by samuel.fm and committed by

GitHub ffc63dc8 4b32b0a7

+322 -47
+4 -1
src/App.native.tsx
··· 54 54 import {readLastActiveAccount} from '#/state/session/util' 55 55 import {Provider as ShellStateProvider} from '#/state/shell' 56 56 import {Provider as ComposerProvider} from '#/state/shell/composer' 57 + import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar' 57 58 import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' 58 59 import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' 59 60 import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' ··· 209 210 <SafeAreaProvider 210 211 initialMetrics={initialWindowMetrics}> 211 212 <IntentDialogProvider> 212 - <InnerApp /> 213 + <LightStatusBarProvider> 214 + <InnerApp /> 215 + </LightStatusBarProvider> 213 216 </IntentDialogProvider> 214 217 </SafeAreaProvider> 215 218 </StarterPackProvider>
+4 -1
src/App.web.tsx
··· 40 40 import {readLastActiveAccount} from '#/state/session/util' 41 41 import {Provider as ShellStateProvider} from '#/state/shell' 42 42 import {Provider as ComposerProvider} from '#/state/shell/composer' 43 + import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar' 43 44 import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' 44 45 import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' 45 46 import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' ··· 181 182 <PortalProvider> 182 183 <StarterPackProvider> 183 184 <IntentDialogProvider> 184 - <InnerApp /> 185 + <LightStatusBarProvider> 186 + <InnerApp /> 187 + </LightStatusBarProvider> 185 188 </IntentDialogProvider> 186 189 </StarterPackProvider> 187 190 </PortalProvider>
+2 -1
src/components/Layout/Header/index.tsx
··· 175 175 gtMobile && a.text_xl, 176 176 style, 177 177 ]} 178 - numberOfLines={2}> 178 + numberOfLines={2} 179 + emoji> 179 180 {children} 180 181 </Text> 181 182 )
+12 -3
src/screens/Profile/Header/GrowableBanner.tsx
··· 10 10 useAnimatedReaction, 11 11 useAnimatedStyle, 12 12 } from 'react-native-reanimated' 13 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 13 14 import {BlurView} from 'expo-blur' 14 15 import {useIsFetching} from '@tanstack/react-query' 15 16 ··· 32 33 }) { 33 34 const pagerContext = usePagerHeaderContext() 34 35 35 - // pagerContext should only be present on iOS, but better safe than sorry 36 + // plain non-growable mode for Android/Web 36 37 if (!pagerContext || !isIOS) { 37 38 return ( 38 39 <View style={[a.w_full, a.h_full]}> ··· 60 61 backButton?: React.ReactNode 61 62 children: React.ReactNode 62 63 }) { 64 + const {top: topInset} = useSafeAreaInsets() 63 65 const isFetching = useIsProfileFetching() 64 66 const animateSpinner = useShouldAnimateSpinner({isFetching, scrollY}) 65 67 ··· 104 106 const animatedBackButtonStyle = useAnimatedStyle(() => ({ 105 107 transform: [ 106 108 { 107 - translateY: interpolate(scrollY.get(), [-150, 60], [-150, 60], { 109 + translateY: interpolate(scrollY.get(), [-150, 10], [-150, 10], { 108 110 extrapolateRight: Extrapolation.CLAMP, 109 111 }), 110 112 }, ··· 128 130 animatedProps={animatedBlurViewProps} 129 131 /> 130 132 </Animated.View> 131 - <View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}> 133 + <View 134 + style={[ 135 + a.absolute, 136 + a.inset_0, 137 + {top: topInset - (isIOS ? 15 : 0)}, 138 + a.justify_center, 139 + a.align_center, 140 + ]}> 132 141 <Animated.View style={[animatedSpinnerStyle]}> 133 142 <ActivityIndicator 134 143 key={animateSpinner ? 'spin' : 'stop'}
+19 -12
src/screens/Profile/Header/Shell.tsx
··· 1 1 import React, {memo} from 'react' 2 2 import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' 3 3 import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated' 4 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 5 import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' 5 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 6 6 import {msg} from '@lingui/macro' 7 7 import {useLingui} from '@lingui/react' 8 8 import {useNavigation} from '@react-navigation/native' 9 9 10 10 import {BACK_HITSLOP} from '#/lib/constants' 11 11 import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef' 12 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 13 12 import {NavigationProp} from '#/lib/routes/types' 14 13 import {isIOS} from '#/platform/detection' 15 14 import {Shadow} from '#/state/cache/types' ··· 18 17 import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 19 18 import {UserAvatar} from '#/view/com/util/UserAvatar' 20 19 import {UserBanner} from '#/view/com/util/UserBanner' 21 - import {atoms as a, useTheme} from '#/alf' 20 + import {atoms as a, platform, useTheme} from '#/alf' 21 + import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' 22 22 import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' 23 23 import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' 24 24 import {GrowableAvatar} from './GrowableAvatar' 25 25 import {GrowableBanner} from './GrowableBanner' 26 + import {StatusBarShadow} from './StatusBarShadow' 26 27 27 28 interface Props { 28 29 profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> ··· 43 44 const {_} = useLingui() 44 45 const {openLightbox} = useLightboxControls() 45 46 const navigation = useNavigation<NavigationProp>() 46 - const {isDesktop} = useWebMediaQueries() 47 + const {top: topInset} = useSafeAreaInsets() 48 + 47 49 const aviRef = useHandleRef() 48 50 49 51 const onPressBack = React.useCallback(() => { ··· 100 102 <View 101 103 pointerEvents={isIOS ? 'auto' : 'box-none'} 102 104 style={[a.relative, {height: 150}]}> 105 + <StatusBarShadow /> 103 106 <GrowableBanner 104 107 backButton={ 105 108 <> 106 - {!isDesktop && !hideBackButton && ( 109 + {!hideBackButton && ( 107 110 <TouchableWithoutFeedback 108 111 testID="profileHeaderBackBtn" 109 112 onPress={onPressBack} ··· 111 114 accessibilityRole="button" 112 115 accessibilityLabel={_(msg`Back`)} 113 116 accessibilityHint=""> 114 - <View style={styles.backBtnWrapper}> 115 - <FontAwesomeIcon 116 - size={18} 117 - icon="angle-left" 118 - color="white" 119 - /> 117 + <View 118 + style={[ 119 + styles.backBtnWrapper, 120 + { 121 + top: platform({ 122 + web: 10, 123 + default: topInset, 124 + }), 125 + }, 126 + ]}> 127 + <ArrowLeftIcon size="lg" fill="white" /> 120 128 </View> 121 129 </TouchableWithoutFeedback> 122 130 )} ··· 186 194 const styles = StyleSheet.create({ 187 195 backBtnWrapper: { 188 196 position: 'absolute', 189 - top: 10, 190 197 left: 10, 191 198 width: 30, 192 199 height: 30,
+56
src/screens/Profile/Header/StatusBarShadow.tsx
··· 1 + import Animated, {SharedValue, useAnimatedStyle} from 'react-native-reanimated' 2 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 3 + import {LinearGradient} from 'expo-linear-gradient' 4 + 5 + import {isIOS} from '#/platform/detection' 6 + import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 7 + import {atoms as a} from '#/alf' 8 + 9 + const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient) 10 + 11 + export function StatusBarShadow() { 12 + const {top: topInset} = useSafeAreaInsets() 13 + const pagerContext = usePagerHeaderContext() 14 + 15 + if (isIOS && pagerContext) { 16 + const {scrollY} = pagerContext 17 + return <StatusBarShadowInnner scrollY={scrollY} /> 18 + } 19 + 20 + return ( 21 + <LinearGradient 22 + colors={['rgba(0,0,0,0.5)', 'rgba(0,0,0,0)']} 23 + style={[ 24 + a.absolute, 25 + a.z_10, 26 + {height: topInset, top: 0, left: 0, right: 0}, 27 + ]} 28 + /> 29 + ) 30 + } 31 + 32 + function StatusBarShadowInnner({scrollY}: {scrollY: SharedValue<number>}) { 33 + const {top: topInset} = useSafeAreaInsets() 34 + 35 + const animatedStyle = useAnimatedStyle(() => { 36 + return { 37 + transform: [ 38 + { 39 + translateY: Math.min(0, scrollY.get()), 40 + }, 41 + ], 42 + } 43 + }) 44 + 45 + return ( 46 + <AnimatedLinearGradient 47 + colors={['rgba(0,0,0,0.5)', 'rgba(0,0,0,0)']} 48 + style={[ 49 + animatedStyle, 50 + a.absolute, 51 + a.z_10, 52 + {height: topInset, top: 0, left: 0, right: 0}, 53 + ]} 54 + /> 55 + ) 56 + }
+3
src/screens/Profile/Header/StatusBarShadow.web.tsx
··· 1 + export function StatusBarShadow() { 2 + return null 3 + }
+112 -7
src/screens/Profile/Header/index.tsx
··· 1 - import React, {memo} from 'react' 2 - import {StyleSheet, View} from 'react-native' 1 + import React, {memo, useState} from 'react' 2 + import {LayoutChangeEvent, StyleSheet, View} from 'react-native' 3 + import Animated, { 4 + runOnJS, 5 + useAnimatedReaction, 6 + useAnimatedStyle, 7 + withTiming, 8 + } from 'react-native-reanimated' 9 + import {useSafeAreaInsets} from 'react-native-safe-area-context' 3 10 import { 4 11 AppBskyActorDefs, 5 12 AppBskyLabelerDefs, 6 13 ModerationOpts, 7 14 RichText as RichTextAPI, 8 15 } from '@atproto/api' 16 + import {useIsFocused} from '@react-navigation/native' 9 17 18 + import {isNative} from '#/platform/detection' 19 + import {useSetLightStatusBar} from '#/state/shell/light-status-bar' 20 + import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 10 21 import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 11 - import {useTheme} from '#/alf' 22 + import {atoms as a, useTheme} from '#/alf' 12 23 import {ProfileHeaderLabeler} from './ProfileHeaderLabeler' 13 24 import {ProfileHeaderStandard} from './ProfileHeaderStandard' 14 25 ··· 43 54 moderationOpts: ModerationOpts 44 55 hideBackButton?: boolean 45 56 isPlaceholderProfile?: boolean 57 + setMinimumHeight: (height: number) => void 46 58 } 47 59 48 - let ProfileHeader = (props: Props): React.ReactNode => { 60 + let ProfileHeader = ({setMinimumHeight, ...props}: Props): React.ReactNode => { 61 + let content 49 62 if (props.profile.associated?.labeler) { 50 63 if (!props.labeler) { 51 - return <ProfileHeaderLoading /> 64 + content = <ProfileHeaderLoading /> 65 + } else { 66 + content = <ProfileHeaderLabeler {...props} labeler={props.labeler} /> 52 67 } 53 - return <ProfileHeaderLabeler {...props} labeler={props.labeler} /> 68 + } else { 69 + content = <ProfileHeaderStandard {...props} /> 54 70 } 55 - return <ProfileHeaderStandard {...props} /> 71 + 72 + return ( 73 + <> 74 + {isNative && ( 75 + <MinimalHeader 76 + onLayout={evt => setMinimumHeight(evt.nativeEvent.layout.height)} 77 + profile={props.profile} 78 + hideBackButton={props.hideBackButton} 79 + /> 80 + )} 81 + {content} 82 + </> 83 + ) 56 84 } 57 85 ProfileHeader = memo(ProfileHeader) 58 86 export {ProfileHeader} 87 + 88 + const MinimalHeader = React.memo(function MinimalHeader({ 89 + onLayout, 90 + }: { 91 + onLayout: (e: LayoutChangeEvent) => void 92 + profile: AppBskyActorDefs.ProfileViewDetailed 93 + hideBackButton?: boolean 94 + }) { 95 + const t = useTheme() 96 + const insets = useSafeAreaInsets() 97 + const ctx = usePagerHeaderContext() 98 + const [visible, setVisible] = useState(false) 99 + const [minimalHeaderHeight, setMinimalHeaderHeight] = React.useState(0) 100 + const isScreenFocused = useIsFocused() 101 + if (!ctx) throw new Error('MinimalHeader cannot be used on web') 102 + const {scrollY, headerHeight} = ctx 103 + 104 + const animatedStyle = useAnimatedStyle(() => { 105 + // if we don't yet have the min header height in JS, hide 106 + if (!_WORKLET || minimalHeaderHeight === 0) { 107 + return { 108 + opacity: 0, 109 + } 110 + } 111 + const pastThreshold = scrollY.get() > 100 112 + return { 113 + opacity: pastThreshold 114 + ? withTiming(1, {duration: 75}) 115 + : withTiming(0, {duration: 75}), 116 + transform: [ 117 + { 118 + translateY: Math.min( 119 + scrollY.get(), 120 + headerHeight - minimalHeaderHeight, 121 + ), 122 + }, 123 + ], 124 + } 125 + }) 126 + 127 + useAnimatedReaction( 128 + () => scrollY.get() > 100, 129 + (value, prev) => { 130 + if (prev !== value) { 131 + runOnJS(setVisible)(value) 132 + } 133 + }, 134 + ) 135 + 136 + useSetLightStatusBar(isScreenFocused && !visible) 137 + 138 + return ( 139 + <Animated.View 140 + pointerEvents={visible ? 'auto' : 'none'} 141 + aria-hidden={!visible} 142 + accessibilityElementsHidden={!visible} 143 + importantForAccessibility={visible ? 'auto' : 'no-hide-descendants'} 144 + onLayout={evt => { 145 + setMinimalHeaderHeight(evt.nativeEvent.layout.height) 146 + onLayout(evt) 147 + }} 148 + style={[ 149 + a.absolute, 150 + a.z_50, 151 + t.atoms.bg, 152 + { 153 + top: 0, 154 + left: 0, 155 + right: 0, 156 + paddingTop: insets.top, 157 + }, 158 + animatedStyle, 159 + ]} 160 + /> 161 + ) 162 + }) 163 + MinimalHeader.displayName = 'MinimalHeader' 59 164 60 165 const styles = StyleSheet.create({ 61 166 avi: {
+45
src/state/shell/light-status-bar.tsx
··· 1 + import {createContext, useContext, useEffect, useState} from 'react' 2 + 3 + import {isWeb} from '#/platform/detection' 4 + import {IS_DEV} from '#/env' 5 + 6 + const LightStatusBarRefCountContext = createContext<boolean>(false) 7 + const SetLightStatusBarRefCountContext = createContext<React.Dispatch< 8 + React.SetStateAction<number> 9 + > | null>(null) 10 + 11 + export function useLightStatusBar() { 12 + return useContext(LightStatusBarRefCountContext) 13 + } 14 + 15 + export function useSetLightStatusBar(enabled: boolean) { 16 + const setRefCount = useContext(SetLightStatusBarRefCountContext) 17 + useEffect(() => { 18 + // noop on web -sfn 19 + if (isWeb) return 20 + 21 + if (!setRefCount) { 22 + if (IS_DEV) 23 + console.error( 24 + 'useLightStatusBar was used without a SetLightStatusBarRefCountContext provider', 25 + ) 26 + return 27 + } 28 + if (enabled) { 29 + setRefCount(prev => prev + 1) 30 + return () => setRefCount(prev => prev - 1) 31 + } 32 + }, [enabled, setRefCount]) 33 + } 34 + 35 + export function Provider({children}: React.PropsWithChildren<{}>) { 36 + const [refCount, setRefCount] = useState(0) 37 + 38 + return ( 39 + <SetLightStatusBarRefCountContext.Provider value={setRefCount}> 40 + <LightStatusBarRefCountContext.Provider value={refCount > 0}> 41 + {children} 42 + </LightStatusBarRefCountContext.Provider> 43 + </SetLightStatusBarRefCountContext.Provider> 44 + ) 45 + }
+19 -11
src/view/com/pager/PagerHeaderContext.tsx
··· 1 1 import React, {useContext} from 'react' 2 2 import {SharedValue} from 'react-native-reanimated' 3 3 4 - import {isIOS} from '#/platform/detection' 4 + import {isNative} from '#/platform/detection' 5 5 6 - export const PagerHeaderContext = 7 - React.createContext<SharedValue<number> | null>(null) 6 + export const PagerHeaderContext = React.createContext<{ 7 + scrollY: SharedValue<number> 8 + headerHeight: number 9 + } | null>(null) 8 10 9 11 /** 10 - * Passes the scrollY value to the pager header's banner, so it can grow on 11 - * overscroll on iOS. Not necessary to use this context provider on other platforms. 12 + * Passes information about the scroll position and header height down via 13 + * context for the pager header to consume. 12 14 * 13 - * @platform ios 15 + * @platform ios, android 14 16 */ 15 17 export function PagerHeaderProvider({ 16 18 scrollY, 19 + headerHeight, 17 20 children, 18 21 }: { 19 22 scrollY: SharedValue<number> 23 + headerHeight: number 20 24 children: React.ReactNode 21 25 }) { 26 + const value = React.useMemo( 27 + () => ({scrollY, headerHeight}), 28 + [scrollY, headerHeight], 29 + ) 22 30 return ( 23 - <PagerHeaderContext.Provider value={scrollY}> 31 + <PagerHeaderContext.Provider value={value}> 24 32 {children} 25 33 </PagerHeaderContext.Provider> 26 34 ) 27 35 } 28 36 29 37 export function usePagerHeaderContext() { 30 - const scrollY = useContext(PagerHeaderContext) 31 - if (isIOS) { 32 - if (!scrollY) { 38 + const ctx = useContext(PagerHeaderContext) 39 + if (isNative) { 40 + if (!ctx) { 33 41 throw new Error( 34 42 'usePagerHeaderContext must be used within a HeaderProvider', 35 43 ) 36 44 } 37 - return {scrollY} 45 + return ctx 38 46 } else { 39 47 return null 40 48 }
+20 -5
src/view/com/pager/PagerWithHeader.tsx
··· 38 38 | ((props: PagerWithHeaderChildParams) => JSX.Element) 39 39 items: string[] 40 40 isHeaderReady: boolean 41 - renderHeader?: () => JSX.Element 41 + renderHeader?: ({ 42 + setMinimumHeight, 43 + }: { 44 + setMinimumHeight: (height: number) => void 45 + }) => JSX.Element 42 46 initialPage?: number 43 47 onPageSelected?: (index: number) => void 44 48 onCurrentPageSelected?: (index: number) => void ··· 83 87 const renderTabBar = React.useCallback( 84 88 (props: RenderTabBarFnProps) => { 85 89 return ( 86 - <PagerHeaderProvider scrollY={scrollY}> 90 + <PagerHeaderProvider 91 + scrollY={scrollY} 92 + headerHeight={headerOnlyHeight}> 87 93 <PagerTabBar 88 94 headerOnlyHeight={headerOnlyHeight} 89 95 items={items} ··· 237 243 items: string[] 238 244 testID?: string 239 245 scrollY: SharedValue<number> 240 - renderHeader?: () => JSX.Element 246 + renderHeader?: ({ 247 + setMinimumHeight, 248 + }: { 249 + setMinimumHeight: (height: number) => void 250 + }) => JSX.Element 241 251 onHeaderOnlyLayout: (height: number) => void 242 252 onTabBarLayout: (e: LayoutChangeEvent) => void 243 253 onCurrentPageSelected?: (index: number) => void ··· 246 256 dragProgress: SharedValue<number> 247 257 dragState: SharedValue<'idle' | 'dragging' | 'settling'> 248 258 }): React.ReactNode => { 259 + const [minimumHeaderHeight, setMinimumHeaderHeight] = React.useState(0) 249 260 const headerTransform = useAnimatedStyle(() => { 250 - const translateY = Math.min(scrollY.get(), headerOnlyHeight) * -1 261 + const translateY = 262 + Math.min( 263 + scrollY.get(), 264 + Math.max(headerOnlyHeight - minimumHeaderHeight, 0), 265 + ) * -1 251 266 return { 252 267 transform: [ 253 268 { ··· 267 282 ref={headerRef} 268 283 pointerEvents={isIOS ? 'auto' : 'box-none'} 269 284 collapsable={false}> 270 - {renderHeader?.()} 285 + {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})} 271 286 { 272 287 // It wouldn't be enough to place `onLayout` on the parent node because 273 288 // this would risk measuring before `isHeaderReady` has turned `true`.
+13 -3
src/view/com/pager/PagerWithHeader.web.tsx
··· 21 21 | ((props: PagerWithHeaderChildParams) => JSX.Element) 22 22 items: string[] 23 23 isHeaderReady: boolean 24 - renderHeader?: () => JSX.Element 24 + renderHeader?: ({ 25 + setMinimumHeight, 26 + }: { 27 + setMinimumHeight: () => void 28 + }) => JSX.Element 25 29 initialPage?: number 26 30 onPageSelected?: (index: number) => void 27 31 onCurrentPageSelected?: (index: number) => void ··· 115 119 currentPage: number 116 120 items: string[] 117 121 testID?: string 118 - renderHeader?: () => JSX.Element 122 + renderHeader?: ({ 123 + setMinimumHeight, 124 + }: { 125 + setMinimumHeight: () => void 126 + }) => JSX.Element 119 127 isHeaderReady: boolean 120 128 onCurrentPageSelected?: (index: number) => void 121 129 onSelect?: (index: number) => void ··· 123 131 }): React.ReactNode => { 124 132 return ( 125 133 <> 126 - <Layout.Center>{renderHeader?.()}</Layout.Center> 134 + <Layout.Center>{renderHeader?.({setMinimumHeight: noop})}</Layout.Center> 127 135 {tabBarAnchor} 128 136 <Layout.Center 129 137 style={web([ ··· 175 183 } 176 184 return [v] 177 185 } 186 + 187 + function noop() {}
+8 -2
src/view/screens/Profile.tsx
··· 43 43 import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header' 44 44 import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed' 45 45 import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels' 46 + import {atoms as a} from '#/alf' 46 47 import * as Layout from '#/components/Layout' 47 48 import {ScreenHider} from '#/components/moderation/ScreenHider' 48 49 import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks' ··· 56 57 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'> 57 58 export function ProfileScreen(props: Props) { 58 59 return ( 59 - <Layout.Screen testID="profileScreen"> 60 + <Layout.Screen testID="profileScreen" style={[a.pt_0]}> 60 61 <ProfileScreenInner {...props} /> 61 62 </Layout.Screen> 62 63 ) ··· 329 330 // rendering 330 331 // = 331 332 332 - const renderHeader = () => { 333 + const renderHeader = ({ 334 + setMinimumHeight, 335 + }: { 336 + setMinimumHeight: (height: number) => void 337 + }) => { 333 338 return ( 334 339 <ExpoScrollForwarderView scrollViewTag={scrollViewTag}> 335 340 <ProfileHeader ··· 339 344 moderationOpts={moderationOpts} 340 345 hideBackButton={hideBackButton} 341 346 isPlaceholderProfile={showPlaceholder} 347 + setMinimumHeight={setMinimumHeight} 342 348 /> 343 349 </ExpoScrollForwarderView> 344 350 )
+5 -1
src/view/shell/index.tsx
··· 18 18 useIsDrawerSwipeDisabled, 19 19 useSetDrawerOpen, 20 20 } from '#/state/shell' 21 + import {useLightStatusBar} from '#/state/shell/light-status-bar' 21 22 import {useCloseAnyActiveElement} from '#/state/util' 22 23 import {Lightbox} from '#/view/com/lightbox/Lightbox' 23 24 import {ModalsContainer} from '#/view/com/modals/Modal' ··· 154 155 155 156 export const Shell: React.FC = function ShellImpl() { 156 157 const {fullyExpandedCount} = useDialogStateControlContext() 158 + const lightStatusBar = useLightStatusBar() 157 159 const t = useTheme() 158 160 useIntentHandler() 159 161 ··· 165 167 <View testID="mobileShellView" style={[a.h_full, t.atoms.bg]}> 166 168 <StatusBar 167 169 style={ 168 - t.name !== 'light' || (isIOS && fullyExpandedCount > 0) 170 + t.name !== 'light' || 171 + (isIOS && fullyExpandedCount > 0) || 172 + lightStatusBar 169 173 ? 'light' 170 174 : 'dark' 171 175 }