diff options
-rw-r--r-- | src/App.native.tsx | 5 | ||||
-rw-r--r-- | src/App.web.tsx | 5 | ||||
-rw-r--r-- | src/components/Layout/Header/index.tsx | 3 | ||||
-rw-r--r-- | src/screens/Profile/Header/GrowableBanner.tsx | 15 | ||||
-rw-r--r-- | src/screens/Profile/Header/Shell.tsx | 31 | ||||
-rw-r--r-- | src/screens/Profile/Header/StatusBarShadow.tsx | 56 | ||||
-rw-r--r-- | src/screens/Profile/Header/StatusBarShadow.web.tsx | 3 | ||||
-rw-r--r-- | src/screens/Profile/Header/index.tsx | 119 | ||||
-rw-r--r-- | src/state/shell/light-status-bar.tsx | 45 | ||||
-rw-r--r-- | src/view/com/pager/PagerHeaderContext.tsx | 30 | ||||
-rw-r--r-- | src/view/com/pager/PagerWithHeader.tsx | 25 | ||||
-rw-r--r-- | src/view/com/pager/PagerWithHeader.web.tsx | 16 | ||||
-rw-r--r-- | src/view/screens/Profile.tsx | 10 | ||||
-rw-r--r-- | src/view/shell/index.tsx | 6 |
14 files changed, 322 insertions, 47 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx index bc38eec79..c22a66e82 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -54,6 +54,7 @@ import { import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' import {Provider as ComposerProvider} from '#/state/shell/composer' +import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' @@ -209,7 +210,9 @@ function App() { <SafeAreaProvider initialMetrics={initialWindowMetrics}> <IntentDialogProvider> - <InnerApp /> + <LightStatusBarProvider> + <InnerApp /> + </LightStatusBarProvider> </IntentDialogProvider> </SafeAreaProvider> </StarterPackProvider> diff --git a/src/App.web.tsx b/src/App.web.tsx index 808b0fc27..b7c5a5633 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -40,6 +40,7 @@ import { import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' import {Provider as ComposerProvider} from '#/state/shell/composer' +import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' @@ -181,7 +182,9 @@ function App() { <PortalProvider> <StarterPackProvider> <IntentDialogProvider> - <InnerApp /> + <LightStatusBarProvider> + <InnerApp /> + </LightStatusBarProvider> </IntentDialogProvider> </StarterPackProvider> </PortalProvider> diff --git a/src/components/Layout/Header/index.tsx b/src/components/Layout/Header/index.tsx index f05350dca..16b484cea 100644 --- a/src/components/Layout/Header/index.tsx +++ b/src/components/Layout/Header/index.tsx @@ -175,7 +175,8 @@ export function TitleText({ gtMobile && a.text_xl, style, ]} - numberOfLines={2}> + numberOfLines={2} + emoji> {children} </Text> ) diff --git a/src/screens/Profile/Header/GrowableBanner.tsx b/src/screens/Profile/Header/GrowableBanner.tsx index 7f5a3cd6e..3d2830439 100644 --- a/src/screens/Profile/Header/GrowableBanner.tsx +++ b/src/screens/Profile/Header/GrowableBanner.tsx @@ -10,6 +10,7 @@ import Animated, { useAnimatedReaction, useAnimatedStyle, } from 'react-native-reanimated' +import {useSafeAreaInsets} from 'react-native-safe-area-context' import {BlurView} from 'expo-blur' import {useIsFetching} from '@tanstack/react-query' @@ -32,7 +33,7 @@ export function GrowableBanner({ }) { const pagerContext = usePagerHeaderContext() - // pagerContext should only be present on iOS, but better safe than sorry + // plain non-growable mode for Android/Web if (!pagerContext || !isIOS) { return ( <View style={[a.w_full, a.h_full]}> @@ -60,6 +61,7 @@ function GrowableBannerInner({ backButton?: React.ReactNode children: React.ReactNode }) { + const {top: topInset} = useSafeAreaInsets() const isFetching = useIsProfileFetching() const animateSpinner = useShouldAnimateSpinner({isFetching, scrollY}) @@ -104,7 +106,7 @@ function GrowableBannerInner({ const animatedBackButtonStyle = useAnimatedStyle(() => ({ transform: [ { - translateY: interpolate(scrollY.get(), [-150, 60], [-150, 60], { + translateY: interpolate(scrollY.get(), [-150, 10], [-150, 10], { extrapolateRight: Extrapolation.CLAMP, }), }, @@ -128,7 +130,14 @@ function GrowableBannerInner({ animatedProps={animatedBlurViewProps} /> </Animated.View> - <View style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}> + <View + style={[ + a.absolute, + a.inset_0, + {top: topInset - (isIOS ? 15 : 0)}, + a.justify_center, + a.align_center, + ]}> <Animated.View style={[animatedSpinnerStyle]}> <ActivityIndicator key={animateSpinner ? 'spin' : 'stop'} diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx index 573d38145..dedbfd201 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -1,15 +1,14 @@ import React, {memo} from 'react' import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated' +import {useSafeAreaInsets} from 'react-native-safe-area-context' import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {BACK_HITSLOP} from '#/lib/constants' import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {NavigationProp} from '#/lib/routes/types' import {isIOS} from '#/platform/detection' import {Shadow} from '#/state/cache/types' @@ -18,11 +17,13 @@ import {useSession} from '#/state/session' import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserBanner} from '#/view/com/util/UserBanner' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a, platform, useTheme} from '#/alf' +import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' import {GrowableAvatar} from './GrowableAvatar' import {GrowableBanner} from './GrowableBanner' +import {StatusBarShadow} from './StatusBarShadow' interface Props { profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> @@ -43,7 +44,8 @@ let ProfileHeaderShell = ({ const {_} = useLingui() const {openLightbox} = useLightboxControls() const navigation = useNavigation<NavigationProp>() - const {isDesktop} = useWebMediaQueries() + const {top: topInset} = useSafeAreaInsets() + const aviRef = useHandleRef() const onPressBack = React.useCallback(() => { @@ -100,10 +102,11 @@ let ProfileHeaderShell = ({ <View pointerEvents={isIOS ? 'auto' : 'box-none'} style={[a.relative, {height: 150}]}> + <StatusBarShadow /> <GrowableBanner backButton={ <> - {!isDesktop && !hideBackButton && ( + {!hideBackButton && ( <TouchableWithoutFeedback testID="profileHeaderBackBtn" onPress={onPressBack} @@ -111,12 +114,17 @@ let ProfileHeaderShell = ({ accessibilityRole="button" accessibilityLabel={_(msg`Back`)} accessibilityHint=""> - <View style={styles.backBtnWrapper}> - <FontAwesomeIcon - size={18} - icon="angle-left" - color="white" - /> + <View + style={[ + styles.backBtnWrapper, + { + top: platform({ + web: 10, + default: topInset, + }), + }, + ]}> + <ArrowLeftIcon size="lg" fill="white" /> </View> </TouchableWithoutFeedback> )} @@ -186,7 +194,6 @@ export {ProfileHeaderShell} const styles = StyleSheet.create({ backBtnWrapper: { position: 'absolute', - top: 10, left: 10, width: 30, height: 30, diff --git a/src/screens/Profile/Header/StatusBarShadow.tsx b/src/screens/Profile/Header/StatusBarShadow.tsx new file mode 100644 index 000000000..587b41051 --- /dev/null +++ b/src/screens/Profile/Header/StatusBarShadow.tsx @@ -0,0 +1,56 @@ +import Animated, {SharedValue, useAnimatedStyle} from 'react-native-reanimated' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {LinearGradient} from 'expo-linear-gradient' + +import {isIOS} from '#/platform/detection' +import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' +import {atoms as a} from '#/alf' + +const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient) + +export function StatusBarShadow() { + const {top: topInset} = useSafeAreaInsets() + const pagerContext = usePagerHeaderContext() + + if (isIOS && pagerContext) { + const {scrollY} = pagerContext + return <StatusBarShadowInnner scrollY={scrollY} /> + } + + return ( + <LinearGradient + colors={['rgba(0,0,0,0.5)', 'rgba(0,0,0,0)']} + style={[ + a.absolute, + a.z_10, + {height: topInset, top: 0, left: 0, right: 0}, + ]} + /> + ) +} + +function StatusBarShadowInnner({scrollY}: {scrollY: SharedValue<number>}) { + const {top: topInset} = useSafeAreaInsets() + + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [ + { + translateY: Math.min(0, scrollY.get()), + }, + ], + } + }) + + return ( + <AnimatedLinearGradient + colors={['rgba(0,0,0,0.5)', 'rgba(0,0,0,0)']} + style={[ + animatedStyle, + a.absolute, + a.z_10, + {height: topInset, top: 0, left: 0, right: 0}, + ]} + /> + ) +} diff --git a/src/screens/Profile/Header/StatusBarShadow.web.tsx b/src/screens/Profile/Header/StatusBarShadow.web.tsx new file mode 100644 index 000000000..cd79871ea --- /dev/null +++ b/src/screens/Profile/Header/StatusBarShadow.web.tsx @@ -0,0 +1,3 @@ +export function StatusBarShadow() { + return null +} diff --git a/src/screens/Profile/Header/index.tsx b/src/screens/Profile/Header/index.tsx index deb8063d9..7e4b9bb31 100644 --- a/src/screens/Profile/Header/index.tsx +++ b/src/screens/Profile/Header/index.tsx @@ -1,14 +1,25 @@ -import React, {memo} from 'react' -import {StyleSheet, View} from 'react-native' +import React, {memo, useState} from 'react' +import {LayoutChangeEvent, StyleSheet, View} from 'react-native' +import Animated, { + runOnJS, + useAnimatedReaction, + useAnimatedStyle, + withTiming, +} from 'react-native-reanimated' +import {useSafeAreaInsets} from 'react-native-safe-area-context' import { AppBskyActorDefs, AppBskyLabelerDefs, ModerationOpts, RichText as RichTextAPI, } from '@atproto/api' +import {useIsFocused} from '@react-navigation/native' +import {isNative} from '#/platform/detection' +import {useSetLightStatusBar} from '#/state/shell/light-status-bar' +import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' -import {useTheme} from '#/alf' +import {atoms as a, useTheme} from '#/alf' import {ProfileHeaderLabeler} from './ProfileHeaderLabeler' import {ProfileHeaderStandard} from './ProfileHeaderStandard' @@ -43,20 +54,114 @@ interface Props { moderationOpts: ModerationOpts hideBackButton?: boolean isPlaceholderProfile?: boolean + setMinimumHeight: (height: number) => void } -let ProfileHeader = (props: Props): React.ReactNode => { +let ProfileHeader = ({setMinimumHeight, ...props}: Props): React.ReactNode => { + let content if (props.profile.associated?.labeler) { if (!props.labeler) { - return <ProfileHeaderLoading /> + content = <ProfileHeaderLoading /> + } else { + content = <ProfileHeaderLabeler {...props} labeler={props.labeler} /> } - return <ProfileHeaderLabeler {...props} labeler={props.labeler} /> + } else { + content = <ProfileHeaderStandard {...props} /> } - return <ProfileHeaderStandard {...props} /> + + return ( + <> + {isNative && ( + <MinimalHeader + onLayout={evt => setMinimumHeight(evt.nativeEvent.layout.height)} + profile={props.profile} + hideBackButton={props.hideBackButton} + /> + )} + {content} + </> + ) } ProfileHeader = memo(ProfileHeader) export {ProfileHeader} +const MinimalHeader = React.memo(function MinimalHeader({ + onLayout, +}: { + onLayout: (e: LayoutChangeEvent) => void + profile: AppBskyActorDefs.ProfileViewDetailed + hideBackButton?: boolean +}) { + const t = useTheme() + const insets = useSafeAreaInsets() + const ctx = usePagerHeaderContext() + const [visible, setVisible] = useState(false) + const [minimalHeaderHeight, setMinimalHeaderHeight] = React.useState(0) + const isScreenFocused = useIsFocused() + if (!ctx) throw new Error('MinimalHeader cannot be used on web') + const {scrollY, headerHeight} = ctx + + const animatedStyle = useAnimatedStyle(() => { + // if we don't yet have the min header height in JS, hide + if (!_WORKLET || minimalHeaderHeight === 0) { + return { + opacity: 0, + } + } + const pastThreshold = scrollY.get() > 100 + return { + opacity: pastThreshold + ? withTiming(1, {duration: 75}) + : withTiming(0, {duration: 75}), + transform: [ + { + translateY: Math.min( + scrollY.get(), + headerHeight - minimalHeaderHeight, + ), + }, + ], + } + }) + + useAnimatedReaction( + () => scrollY.get() > 100, + (value, prev) => { + if (prev !== value) { + runOnJS(setVisible)(value) + } + }, + ) + + useSetLightStatusBar(isScreenFocused && !visible) + + return ( + <Animated.View + pointerEvents={visible ? 'auto' : 'none'} + aria-hidden={!visible} + accessibilityElementsHidden={!visible} + importantForAccessibility={visible ? 'auto' : 'no-hide-descendants'} + onLayout={evt => { + setMinimalHeaderHeight(evt.nativeEvent.layout.height) + onLayout(evt) + }} + style={[ + a.absolute, + a.z_50, + t.atoms.bg, + { + top: 0, + left: 0, + right: 0, + paddingTop: insets.top, + }, + animatedStyle, + ]} + /> + ) +}) +MinimalHeader.displayName = 'MinimalHeader' + const styles = StyleSheet.create({ avi: { position: 'absolute', diff --git a/src/state/shell/light-status-bar.tsx b/src/state/shell/light-status-bar.tsx new file mode 100644 index 000000000..eb213adb9 --- /dev/null +++ b/src/state/shell/light-status-bar.tsx @@ -0,0 +1,45 @@ +import {createContext, useContext, useEffect, useState} from 'react' + +import {isWeb} from '#/platform/detection' +import {IS_DEV} from '#/env' + +const LightStatusBarRefCountContext = createContext<boolean>(false) +const SetLightStatusBarRefCountContext = createContext<React.Dispatch< + React.SetStateAction<number> +> | null>(null) + +export function useLightStatusBar() { + return useContext(LightStatusBarRefCountContext) +} + +export function useSetLightStatusBar(enabled: boolean) { + const setRefCount = useContext(SetLightStatusBarRefCountContext) + useEffect(() => { + // noop on web -sfn + if (isWeb) return + + if (!setRefCount) { + if (IS_DEV) + console.error( + 'useLightStatusBar was used without a SetLightStatusBarRefCountContext provider', + ) + return + } + if (enabled) { + setRefCount(prev => prev + 1) + return () => setRefCount(prev => prev - 1) + } + }, [enabled, setRefCount]) +} + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [refCount, setRefCount] = useState(0) + + return ( + <SetLightStatusBarRefCountContext.Provider value={setRefCount}> + <LightStatusBarRefCountContext.Provider value={refCount > 0}> + {children} + </LightStatusBarRefCountContext.Provider> + </SetLightStatusBarRefCountContext.Provider> + ) +} diff --git a/src/view/com/pager/PagerHeaderContext.tsx b/src/view/com/pager/PagerHeaderContext.tsx index fd4cc7463..c979f7a6d 100644 --- a/src/view/com/pager/PagerHeaderContext.tsx +++ b/src/view/com/pager/PagerHeaderContext.tsx @@ -1,40 +1,48 @@ import React, {useContext} from 'react' import {SharedValue} from 'react-native-reanimated' -import {isIOS} from '#/platform/detection' +import {isNative} from '#/platform/detection' -export const PagerHeaderContext = - React.createContext<SharedValue<number> | null>(null) +export const PagerHeaderContext = React.createContext<{ + scrollY: SharedValue<number> + headerHeight: number +} | null>(null) /** - * Passes the scrollY value to the pager header's banner, so it can grow on - * overscroll on iOS. Not necessary to use this context provider on other platforms. + * Passes information about the scroll position and header height down via + * context for the pager header to consume. * - * @platform ios + * @platform ios, android */ export function PagerHeaderProvider({ scrollY, + headerHeight, children, }: { scrollY: SharedValue<number> + headerHeight: number children: React.ReactNode }) { + const value = React.useMemo( + () => ({scrollY, headerHeight}), + [scrollY, headerHeight], + ) return ( - <PagerHeaderContext.Provider value={scrollY}> + <PagerHeaderContext.Provider value={value}> {children} </PagerHeaderContext.Provider> ) } export function usePagerHeaderContext() { - const scrollY = useContext(PagerHeaderContext) - if (isIOS) { - if (!scrollY) { + const ctx = useContext(PagerHeaderContext) + if (isNative) { + if (!ctx) { throw new Error( 'usePagerHeaderContext must be used within a HeaderProvider', ) } - return {scrollY} + return ctx } else { return null } diff --git a/src/view/com/pager/PagerWithHeader.tsx b/src/view/com/pager/PagerWithHeader.tsx index 617445964..dcf141f84 100644 --- a/src/view/com/pager/PagerWithHeader.tsx +++ b/src/view/com/pager/PagerWithHeader.tsx @@ -38,7 +38,11 @@ export interface PagerWithHeaderProps { | ((props: PagerWithHeaderChildParams) => JSX.Element) items: string[] isHeaderReady: boolean - renderHeader?: () => JSX.Element + renderHeader?: ({ + setMinimumHeight, + }: { + setMinimumHeight: (height: number) => void + }) => JSX.Element initialPage?: number onPageSelected?: (index: number) => void onCurrentPageSelected?: (index: number) => void @@ -83,7 +87,9 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>( const renderTabBar = React.useCallback( (props: RenderTabBarFnProps) => { return ( - <PagerHeaderProvider scrollY={scrollY}> + <PagerHeaderProvider + scrollY={scrollY} + headerHeight={headerOnlyHeight}> <PagerTabBar headerOnlyHeight={headerOnlyHeight} items={items} @@ -237,7 +243,11 @@ let PagerTabBar = ({ items: string[] testID?: string scrollY: SharedValue<number> - renderHeader?: () => JSX.Element + renderHeader?: ({ + setMinimumHeight, + }: { + setMinimumHeight: (height: number) => void + }) => JSX.Element onHeaderOnlyLayout: (height: number) => void onTabBarLayout: (e: LayoutChangeEvent) => void onCurrentPageSelected?: (index: number) => void @@ -246,8 +256,13 @@ let PagerTabBar = ({ dragProgress: SharedValue<number> dragState: SharedValue<'idle' | 'dragging' | 'settling'> }): React.ReactNode => { + const [minimumHeaderHeight, setMinimumHeaderHeight] = React.useState(0) const headerTransform = useAnimatedStyle(() => { - const translateY = Math.min(scrollY.get(), headerOnlyHeight) * -1 + const translateY = + Math.min( + scrollY.get(), + Math.max(headerOnlyHeight - minimumHeaderHeight, 0), + ) * -1 return { transform: [ { @@ -267,7 +282,7 @@ let PagerTabBar = ({ ref={headerRef} pointerEvents={isIOS ? 'auto' : 'box-none'} collapsable={false}> - {renderHeader?.()} + {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})} { // It wouldn't be enough to place `onLayout` on the parent node because // this would risk measuring before `isHeaderReady` has turned `true`. diff --git a/src/view/com/pager/PagerWithHeader.web.tsx b/src/view/com/pager/PagerWithHeader.web.tsx index 3335532b3..98b32b347 100644 --- a/src/view/com/pager/PagerWithHeader.web.tsx +++ b/src/view/com/pager/PagerWithHeader.web.tsx @@ -21,7 +21,11 @@ export interface PagerWithHeaderProps { | ((props: PagerWithHeaderChildParams) => JSX.Element) items: string[] isHeaderReady: boolean - renderHeader?: () => JSX.Element + renderHeader?: ({ + setMinimumHeight, + }: { + setMinimumHeight: () => void + }) => JSX.Element initialPage?: number onPageSelected?: (index: number) => void onCurrentPageSelected?: (index: number) => void @@ -115,7 +119,11 @@ let PagerTabBar = ({ currentPage: number items: string[] testID?: string - renderHeader?: () => JSX.Element + renderHeader?: ({ + setMinimumHeight, + }: { + setMinimumHeight: () => void + }) => JSX.Element isHeaderReady: boolean onCurrentPageSelected?: (index: number) => void onSelect?: (index: number) => void @@ -123,7 +131,7 @@ let PagerTabBar = ({ }): React.ReactNode => { return ( <> - <Layout.Center>{renderHeader?.()}</Layout.Center> + <Layout.Center>{renderHeader?.({setMinimumHeight: noop})}</Layout.Center> {tabBarAnchor} <Layout.Center style={web([ @@ -175,3 +183,5 @@ function toArray<T>(v: T | T[]): T[] { } return [v] } + +function noop() {} diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 782e9b9c8..ebf1d955d 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -43,6 +43,7 @@ import {ListRef} from '#/view/com/util/List' import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header' import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed' import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels' +import {atoms as a} from '#/alf' import * as Layout from '#/components/Layout' import {ScreenHider} from '#/components/moderation/ScreenHider' import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks' @@ -56,7 +57,7 @@ interface SectionRef { type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'> export function ProfileScreen(props: Props) { return ( - <Layout.Screen testID="profileScreen"> + <Layout.Screen testID="profileScreen" style={[a.pt_0]}> <ProfileScreenInner {...props} /> </Layout.Screen> ) @@ -329,7 +330,11 @@ function ProfileScreenLoaded({ // rendering // = - const renderHeader = () => { + const renderHeader = ({ + setMinimumHeight, + }: { + setMinimumHeight: (height: number) => void + }) => { return ( <ExpoScrollForwarderView scrollViewTag={scrollViewTag}> <ProfileHeader @@ -339,6 +344,7 @@ function ProfileScreenLoaded({ moderationOpts={moderationOpts} hideBackButton={hideBackButton} isPlaceholderProfile={showPlaceholder} + setMinimumHeight={setMinimumHeight} /> </ExpoScrollForwarderView> ) diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 179e8858e..a5e97610d 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -18,6 +18,7 @@ import { useIsDrawerSwipeDisabled, useSetDrawerOpen, } from '#/state/shell' +import {useLightStatusBar} from '#/state/shell/light-status-bar' import {useCloseAnyActiveElement} from '#/state/util' import {Lightbox} from '#/view/com/lightbox/Lightbox' import {ModalsContainer} from '#/view/com/modals/Modal' @@ -154,6 +155,7 @@ function ShellInner() { export const Shell: React.FC = function ShellImpl() { const {fullyExpandedCount} = useDialogStateControlContext() + const lightStatusBar = useLightStatusBar() const t = useTheme() useIntentHandler() @@ -165,7 +167,9 @@ export const Shell: React.FC = function ShellImpl() { <View testID="mobileShellView" style={[a.h_full, t.atoms.bg]}> <StatusBar style={ - t.name !== 'light' || (isIOS && fullyExpandedCount > 0) + t.name !== 'light' || + (isIOS && fullyExpandedCount > 0) || + lightStatusBar ? 'light' : 'dark' } |