refactor(mobile): improve navigation header and layout components

- Updated `NavigationHeader` to enhance modal presentation handling and UI customization.
- Adjusted `getDefaultHeaderHeight` function to simplify header height calculations for iOS devices.
- Integrated `NativeNavigationHeader` in the terms screen for consistent header behavior.
- Cleaned up layout files by removing unused imports and optimizing component structure.

This refactor aims to streamline navigation and improve the overall user experience across mobile screens.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-03-14 22:20:08 +08:00
parent bfec384151
commit 883ed75bd4
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
8 changed files with 48 additions and 31 deletions

View File

@ -1,6 +1,6 @@
import { cn } from "@follow/utils"
import { getDefaultHeaderHeight, HeaderTitle } from "@react-navigation/elements"
import { router, useNavigation } from "expo-router"
import { HeaderTitle } from "@react-navigation/elements"
import { router, Stack, useNavigation } from "expo-router"
import type { FC, PropsWithChildren, ReactNode } from "react"
import { createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"
import type { LayoutChangeEvent } from "react-native"
@ -14,12 +14,14 @@ import Animated, {
} from "react-native-reanimated"
import type { DefaultStyle } from "react-native-reanimated/lib/typescript/hook/commonTypes"
import { useSafeAreaFrame, useSafeAreaInsets } from "react-native-safe-area-context"
import type { NativeStackNavigationOptions } from "react-native-screens/lib/typescript/native-stack/types"
import type { ViewProps } from "react-native-svg/lib/typescript/fabric/utils"
import { useColor } from "react-native-uikit-colors"
import { MingcuteLeftLineIcon } from "@/src/icons/mingcute_left_line"
import { ThemedBlurView } from "../../common/ThemedBlurView"
import { getDefaultHeaderHeight } from "../utils"
import { NavigationContext } from "../views/NavigationContext"
import { SetNavigationHeaderHeightContext } from "../views/NavigationHeaderContext"
@ -137,9 +139,10 @@ export const InternalNavigationHeader = ({
const insets = useSafeAreaInsets()
const frame = useSafeAreaFrame()
const defaultHeight = useMemo(
() => getDefaultHeaderHeight(frame, modal, insets.top),
() => getDefaultHeaderHeight(frame, modal, modal ? 0 : insets.top),
[frame, insets.top, modal],
)
const border = useColor("opaqueSeparator")
const opacityAnimated = useSharedValue(0)
const { scrollY } = useContext(NavigationContext)!
@ -172,11 +175,8 @@ export const InternalNavigationHeader = ({
)
const rootTitleBarStyle = useAnimatedStyle(() => {
const styles = {
paddingTop: insets.top,
minHeight: defaultHeight,
paddingTop: modal ? 0 : insets.top,
position: "relative",
overflow: "hidden",
} satisfies DefaultStyle
if (hideableBottom) {
@ -316,3 +316,24 @@ export const UINavigationHeaderActionButton = ({
)
}
const Noop = () => null
/**
* NativeNavigationHeader wrapped react navigation native-stack is universal in modal and stack, but there are significant limitations in UI customization.
*/
export interface NativeNavigationHeaderProps
extends Pick<NativeStackNavigationOptions, "headerLeft" | "headerRight" | "headerTitle"> {}
export const NativeNavigationHeader: FC<NativeNavigationHeaderProps> = (props) => {
const navigation = useNavigation()
return (
<Stack.Screen
options={{
headerShown: true,
headerLeft: () => <DefaultHeaderBackButton canGoBack={navigation.canGoBack()} />,
headerTransparent: true,
headerBlurEffect: "systemChromeMaterial",
...props,
}}
/>
)
}

View File

@ -18,7 +18,7 @@ export function getDefaultHeaderHeight(
const isLandscape = layout.width > layout.height
if (Platform.OS === "ios" && (Platform.isPad || Platform.isTV)) {
if (Platform.OS === "ios" && Platform.isPad) {
if (modalPresentation) {
headerHeight = 56
} else {

View File

@ -137,6 +137,7 @@ export const NavigationBlurEffectHeader = ({
<NavigationContext.Provider value={navigationContext}>
<SetNavigationHeaderHeightContext.Provider value={setHeaderHeight}>
<InternalNavigationHeader
{...options}
modal={options.presentation === "modal" || options.presentation === "formSheet"}
title={options.title}
headerRight={options.headerRight}

View File

@ -135,7 +135,7 @@ export function EmailLogin() {
className="mt-8"
/>
<TouchableOpacity className="mx-auto mt-10" onPress={() => router.push("/sign-up")}>
<Text className="text-accent m-1 text-[15px]">Don't have an account? Sign Up</Text>
<Text className="text-accent m-1 text-[15px]">Don't have an account?</Text>
</TouchableOpacity>
<TouchableOpacity className="mx-auto mt-4" onPress={() => router.push("/forget-password")}>
<Text className="text-secondary-label m-[6] text-[15px]">Forgot password?</Text>

View File

@ -1,16 +1,10 @@
import { Stack } from "expo-router"
import { useColorScheme } from "react-native"
import { getSystemBackgroundColor } from "@/src/theme/utils"
export default function HeadlessLayout() {
useColorScheme()
const systemBackgroundColor = getSystemBackgroundColor()
return (
<Stack
screenOptions={{
contentStyle: { backgroundColor: systemBackgroundColor },
contentStyle: { backgroundColor: "#000" },
headerShown: false,
}}
/>

View File

@ -1,11 +1,6 @@
import { useMemo } from "react"
import { useAnimatedValue } from "react-native"
import { ScrollView } from "react-native"
import { NavigationContext } from "@/src/components/layouts/views/NavigationContext"
import {
NavigationBlurEffectHeader,
SafeNavigationScrollView,
} from "@/src/components/layouts/views/SafeNavigationScrollView"
import { NativeNavigationHeader } from "@/src/components/layouts/header/NavigationHeader"
import { Markdown } from "@/src/components/ui/typography/Markdown"
const txt = `# Terms of Service
@ -96,15 +91,11 @@ export const TermsMarkdown = () => {
}
export default function Teams() {
const scrollY = useAnimatedValue(100)
return (
<NavigationContext.Provider value={useMemo(() => ({ scrollY }), [scrollY])}>
<SafeNavigationScrollView>
<NavigationBlurEffectHeader headerShown title="Terms of Service" />
<ScrollView className="bg-system-background" contentInsetAdjustmentBehavior="always">
<NativeNavigationHeader headerTitle="Terms of Service" />
<TermsMarkdown />
</SafeNavigationScrollView>
</NavigationContext.Provider>
<TermsMarkdown />
</ScrollView>
)
}

View File

@ -9,30 +9,35 @@ export default function ModalLayout() {
name="add"
options={{
title: "Add Subscription",
presentation: "modal",
}}
/>
<Stack.Screen
name="rsshub-form"
options={{
title: "RSSHub Form",
presentation: "modal",
}}
/>
<Stack.Screen
name="follow"
options={{
title: "Follow",
presentation: "modal",
}}
/>
<Stack.Screen
name="list"
options={{
title: "List",
presentation: "modal",
}}
/>
<Stack.Screen
name="profile"
options={{
title: "Profile",
presentation: "modal",
headerShown: false,
}}
/>
@ -42,6 +47,7 @@ export default function ModalLayout() {
headerShown: true,
title: "",
headerTransparent: true,
presentation: "modal",
headerLeft: ModalHeaderCloseButton,
}}
/>
@ -51,6 +57,7 @@ export default function ModalLayout() {
headerShown: true,
title: "",
headerTransparent: true,
presentation: "modal",
headerLeft: ModalHeaderCloseButton,
}}
/>
@ -61,6 +68,7 @@ export default function ModalLayout() {
title: "",
headerTransparent: true,
headerLeft: ModalHeaderCloseButton,
presentation: "modal",
}}
/>
<Stack.Screen
@ -70,6 +78,7 @@ export default function ModalLayout() {
title: "",
headerTransparent: true,
headerLeft: ModalHeaderCloseButton,
presentation: "modal",
}}
/>
</Stack>

View File

@ -36,6 +36,7 @@ export default function RootLayout() {
<View className="flex-1 bg-black">
<RootProviders>
<Session />
<AnimatedStack />
{__DEV__ && <DebugButton />}