Skip to content

Commit

Permalink
feat: hideable header bottom
Browse files Browse the repository at this point in the history
  • Loading branch information
DIYgod committed Jan 23, 2025
1 parent 44c6fd0 commit b0012c1
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 17 deletions.
52 changes: 48 additions & 4 deletions apps/mobile/src/components/common/SafeNavigationScrollView.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs"
import { useHeaderHeight } from "@react-navigation/elements"
import { Header, useHeaderHeight } from "@react-navigation/elements"
import type { NativeStackNavigationOptions } from "@react-navigation/native-stack"
import { router, Stack, useNavigation } from "expo-router"
import type { FC, PropsWithChildren } from "react"
import { createContext, useContext, useEffect, useMemo, useState } from "react"
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react"
import type { ScrollViewProps } from "react-native"
import {
Animated as RNAnimated,
Expand All @@ -12,10 +12,12 @@ import {
useAnimatedValue,
View,
} from "react-native"
import Animated, { useSharedValue, withTiming } from "react-native-reanimated"
import type { ReanimatedScrollEvent } from "react-native-reanimated/lib/typescript/hook/commonTypes"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { useColor } from "react-native-uikit-colors"

import { useDefaultHeaderHeight } from "@/src/hooks/useDefaultHeaderHeight"
import { MingcuteLeftLineIcon } from "@/src/icons/mingcute_left_line"

import { AnimatedScrollView } from "./AnimatedComponents"
Expand Down Expand Up @@ -69,9 +71,13 @@ export const SafeNavigationScrollView: FC<SafeNavigationScrollViewProps> = ({

export const NavigationBlurEffectHeader = ({
blurThreshold = 0,
headerHideableBottomHeight = 50,
headerHideableBottom,
...props
}: NativeStackNavigationOptions & {
blurThreshold?: number
headerHideableBottomHeight?: number
headerHideableBottom?: () => React.ReactNode
}) => {
const label = useColor("label")

Expand All @@ -83,15 +89,39 @@ export const NavigationBlurEffectHeader = ({

const [opacity, setOpacity] = useState(0)

const originalDefaultHeaderHeight = useDefaultHeaderHeight()
const largeDefaultHeaderHeight = originalDefaultHeaderHeight + headerHideableBottomHeight

const largeHeaderHeight = useSharedValue(largeDefaultHeaderHeight)

const lastScrollY = useRef(0)

useEffect(() => {
const id = scrollY.addListener(({ value }) => {
setOpacity(Math.min(1, Math.max(0, Math.min(1, (value + blurThreshold) / 10))))
setOpacity(Math.max(0, Math.min(1, (value + blurThreshold) / 10)))
if (headerHideableBottom && value > 0) {
if (value > lastScrollY.current) {
largeHeaderHeight.value = withTiming(originalDefaultHeaderHeight)
} else {
largeHeaderHeight.value = withTiming(largeDefaultHeaderHeight)
}
lastScrollY.current = value
}
})

return () => {
scrollY.removeListener(id)
}
}, [blurThreshold, scrollY])
}, [
blurThreshold,
scrollY,
headerHideableBottom,
largeHeaderHeight,
originalDefaultHeaderHeight,
largeDefaultHeaderHeight,
])

const hideableBottom = headerHideableBottom?.()

return (
<Stack.Screen
Expand All @@ -116,6 +146,20 @@ export const NavigationBlurEffectHeader = ({
)
: undefined,

header: headerHideableBottom
? ({ options }) => {
return (
<Animated.View style={{ height: largeHeaderHeight }} className="overflow-hidden">
<View pointerEvents="box-none" style={[StyleSheet.absoluteFill]}>
{options.headerBackground?.()}
</View>
<Header title={options.title ?? ""} {...options} headerBackground={() => null} />
{hideableBottom}
</Animated.View>
)
}
: undefined,

...props,
}}
/>
Expand Down
22 changes: 22 additions & 0 deletions apps/mobile/src/hooks/useDefaultHeaderHeight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// https://github.com/react-navigation/react-navigation/blob/main/packages/native-stack/src/views/NativeStackView.native.tsx

import { getDefaultHeaderHeight } from "@react-navigation/elements"
import { Platform } from "react-native"
import { useSafeAreaFrame, useSafeAreaInsets } from "react-native-safe-area-context"

export const useDefaultHeaderHeight = () => {
const insets = useSafeAreaInsets()
const frame = useSafeAreaFrame()

const isIPhone = Platform.OS === "ios" && !(Platform.isPad || Platform.isTV)
const isLandscape = frame.width > frame.height
const topInset = isIPhone && isLandscape ? 0 : insets.top
const ANDROID_DEFAULT_HEADER_HEIGHT = 56

const defaultHeaderHeight = Platform.select({
android: ANDROID_DEFAULT_HEADER_HEIGHT + topInset,
default: getDefaultHeaderHeight(frame, false, topInset),
})

return defaultHeaderHeight
}
8 changes: 6 additions & 2 deletions apps/mobile/src/modules/entry-list/action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export function LeftAction() {
const insets = useActionPadding()

return (
<TouchableOpacity onPress={openDrawer} style={{ paddingLeft: insets.paddingLeft }}>
<TouchableOpacity
onPress={openDrawer}
className="flex-row items-center"
style={{ paddingLeft: insets.paddingLeft }}
>
<LayoutLeftbarOpenCuteReIcon color={accentColor} />
</TouchableOpacity>
)
Expand All @@ -29,7 +33,7 @@ export function RightAction() {
const insets = useActionPadding()

return (
<View className="flex-row items-center gap-4" style={{ paddingRight: insets.paddingRight }}>
<View className="flex-row items-center" style={{ paddingRight: insets.paddingRight }}>
<Link asChild href="/add">
<TouchableOpacity className="size-6">
<AddCuteReIcon color={accentColor} />
Expand Down
7 changes: 5 additions & 2 deletions apps/mobile/src/modules/entry-list/entry-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
NavigationContext,
} from "@/src/components/common/SafeNavigationScrollView"
import { ItemPressable } from "@/src/components/ui/pressable/item-pressable"
import { useSelectedFeed } from "@/src/modules/feed-drawer/atoms"
import { useSelectedFeed, useSelectedFeedTitle } from "@/src/modules/feed-drawer/atoms"
import { useEntry } from "@/src/store/entry/hooks"

import { ViewSelector } from "../feed-drawer/view-selector"
Expand All @@ -25,12 +25,13 @@ export function EntryListScreen({ entryIds }: { entryIds: string[] }) {
const scrollY = useAnimatedValue(0)
const selectedFeed = useSelectedFeed()
const view = selectedFeed.type === "view" ? selectedFeed.viewId : null
const viewTitle = useSelectedFeedTitle()

return (
<NavigationContext.Provider value={useMemo(() => ({ scrollY }), [scrollY])}>
<NavigationBlurEffectHeader
headerShown
headerTitle={ViewSelector}
title={viewTitle}
headerLeft={useCallback(
() => (
<LeftAction />
Expand All @@ -43,6 +44,8 @@ export function EntryListScreen({ entryIds }: { entryIds: string[] }) {
),
[],
)}
headerHideableBottomHeight={45}
headerHideableBottom={ViewSelector}
/>
{view === FeedViewType.Pictures || view === FeedViewType.Videos ? (
<EntryListContentGrid entryIds={entryIds} />
Expand Down
20 changes: 11 additions & 9 deletions apps/mobile/src/modules/feed-drawer/view-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Image } from "expo-image"
import { ScrollView, TouchableOpacity } from "react-native"
import { ScrollView, TouchableOpacity, View } from "react-native"
import { Grayscale } from "react-native-color-matrix-image-filters"

import { FallbackIcon } from "@/src/components/ui/icon/fallback-icon"
Expand All @@ -15,14 +15,16 @@ export function ViewSelector() {
const lists = useAllListSubscription()

return (
<ScrollView horizontal contentContainerClassName="flex-row gap-3 items-center">
{views.map((view) => (
<ViewItem key={view.name} view={view} />
))}
{lists.map((listId) => (
<ListItem key={listId} listId={listId} />
))}
</ScrollView>
<View className="flex items-center justify-between px-3 pt-1">
<ScrollView horizontal contentContainerClassName="flex-row gap-3 items-center">
{views.map((view) => (
<ViewItem key={view.name} view={view} />
))}
{lists.map((listId) => (
<ListItem key={listId} listId={listId} />
))}
</ScrollView>
</View>
)
}

Expand Down

0 comments on commit b0012c1

Please sign in to comment.