Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/mobile/src/screens/profile-screen/ProfileNavOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const PROFILE_NAV_CONTROLS_HEIGHT = 56

// Scroll distance (in px) over which the blur background fades in and the
// button icons transition from white to the neutral theme color.
const FADE_DISTANCE = 60
export const PROFILE_NAV_SCROLL_FADE_PX = 60

const useStyles = makeStyles(({ spacing }) => ({
root: {
Expand Down Expand Up @@ -93,7 +93,7 @@ export const ProfileNavOverlay = () => {

const blurBackgroundStyle = useAnimatedStyle(() => ({
opacity: scrollY
? interpolate(scrollY.value, [0, FADE_DISTANCE], [0, 1], {
? interpolate(scrollY.value, [0, PROFILE_NAV_SCROLL_FADE_PX], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
})
Expand All @@ -103,7 +103,7 @@ export const ProfileNavOverlay = () => {
// White icons fade out as the user scrolls past the cover photo.
const whiteIconsStyle = useAnimatedStyle(() => ({
opacity: scrollY
? interpolate(scrollY.value, [0, FADE_DISTANCE], [1, 0], {
? interpolate(scrollY.value, [0, PROFILE_NAV_SCROLL_FADE_PX], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
})
Expand All @@ -113,7 +113,7 @@ export const ProfileNavOverlay = () => {
// Neutral icons fade in as the user scrolls past the cover photo.
const neutralIconsStyle = useAnimatedStyle(() => ({
opacity: scrollY
? interpolate(scrollY.value, [0, FADE_DISTANCE], [0, 1], {
? interpolate(scrollY.value, [0, PROFILE_NAV_SCROLL_FADE_PX], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
})
Expand All @@ -124,7 +124,7 @@ export const ProfileNavOverlay = () => {
// the cover photo has scrolled away.
const titleStyle = useAnimatedStyle(() => ({
opacity: scrollY
? interpolate(scrollY.value, [0, FADE_DISTANCE], [0, 1], {
? interpolate(scrollY.value, [0, PROFILE_NAV_SCROLL_FADE_PX], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
})
Expand Down
4 changes: 2 additions & 2 deletions packages/mobile/src/screens/profile-screen/ProfileScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ScreenPrimaryContent } from 'app/components/core/Screen/ScreenPrimaryCo
import { ScreenSecondaryContent } from 'app/components/core/Screen/ScreenSecondaryContent'
import { OfflinePlaceholder } from 'app/components/offline-placeholder'
import { useRoute } from 'app/hooks/useRoute'
import { useStatusBarStyle } from 'app/hooks/useStatusBarStyle'
import { makeStyles } from 'app/styles'

import { DeactivatedProfileTombstone } from './DeactivatedProfileTombstone'
Expand All @@ -27,6 +26,7 @@ import { ProfileNavOverlay } from './ProfileNavOverlay'
import { ProfileScreenSkeleton } from './ProfileScreenSkeleton'
import { ProfileScrollContext } from './ProfileScrollContext'
import { ProfileTabNavigator } from './ProfileTabs/ProfileTabNavigator'
import { useProfileScrollStatusBar } from './useProfileScrollStatusBar'
import { useRefreshProfile } from './useRefreshProfile'
const { setCurrentUser: setCurrentUserAction } = profilePageActions
const { getIsReachable } = reachabilitySelectors
Expand Down Expand Up @@ -96,14 +96,14 @@ export const ProfileScreen = () => {
)

useFocusEffect(setCurrentUser)
useStatusBarStyle('light-content')

const renderHeader = useCallback(() => <ProfileHeader />, [])

// Shared value that tracks the current tab's scroll position. Written to
// from inside the collapsible header via `ProfileScrollBridge` and read by
// `ProfileNavOverlay` to animate its blur background and icon colors.
const scrollY = useSharedValue(0)
useProfileScrollStatusBar(scrollY)

return (
<Screen url={handle && `/${encodeUrlName(handle)}`}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { useCallback, useRef } from 'react'

import { useFocusEffect } from '@react-navigation/native'
import type { StatusBarProps } from 'react-native-bars'
import { NavigationBar, StatusBar as RNStatusBar } from 'react-native-bars'
import type { SharedValue } from 'react-native-reanimated'
import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'

import { PROFILE_NAV_SCROLL_FADE_PX } from './ProfileNavOverlay'

/**
* Status bar / nav bar style for profile: light icons over the cover at the top,
* default (dark) system bar content once the header has scrolled up — matching
* `ProfileNavOverlay` icon transition.
*/
export const useProfileScrollStatusBar = (scrollY: SharedValue<number>) => {
const statusBarEntryRef = useRef<StatusBarProps | null>(null)
const navigationBarEntryRef = useRef<StatusBarProps | null>(null)
const lastBarStyleRef = useRef<'light-content' | 'dark-content' | null>(null)

const applyBarStyle = useCallback(
(barStyle: 'light-content' | 'dark-content') => {
if (!statusBarEntryRef.current || !navigationBarEntryRef.current) {
return
}
if (lastBarStyleRef.current === barStyle) {
return
}
lastBarStyleRef.current = barStyle
statusBarEntryRef.current = RNStatusBar.replaceStackEntry(
statusBarEntryRef.current,
{ barStyle }
)
navigationBarEntryRef.current = NavigationBar.replaceStackEntry(
navigationBarEntryRef.current,
{ barStyle }
)
},
[]
)

useFocusEffect(
useCallback(() => {
lastBarStyleRef.current = null
statusBarEntryRef.current = RNStatusBar.pushStackEntry({
barStyle: 'light-content'
})
navigationBarEntryRef.current = NavigationBar.pushStackEntry({
barStyle: 'light-content'
})

// Align with restored scroll position (reaction only fires on change).
requestAnimationFrame(() => {
const barStyle =
scrollY.value < PROFILE_NAV_SCROLL_FADE_PX
? 'light-content'
: 'dark-content'
applyBarStyle(barStyle)
})

return () => {
lastBarStyleRef.current = null
if (statusBarEntryRef.current) {
RNStatusBar.popStackEntry(statusBarEntryRef.current)
statusBarEntryRef.current = null
}
if (navigationBarEntryRef.current) {
NavigationBar.popStackEntry(navigationBarEntryRef.current)
navigationBarEntryRef.current = null
}
}
}, [applyBarStyle, scrollY])
)

useAnimatedReaction(
() => scrollY.value,
(y, prev) => {
const atTop = y < PROFILE_NAV_SCROLL_FADE_PX
const barStyle = atTop ? 'light-content' : 'dark-content'
const prevY = prev ?? 0
const prevAtTop = prevY < PROFILE_NAV_SCROLL_FADE_PX
if (atTop !== prevAtTop) {
runOnJS(applyBarStyle)(barStyle)
}
},
[applyBarStyle]
)
}