feat(mobile): optimize tab bar interaction and scroll behavior
- Extract TabItem component with improved press animations - Simplify native scroll to top implementation - Add cross-platform scroll to top method - Enhance tab bar interaction with animated scaling effect Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
265f23fd76
commit
70e115c73f
|
|
@ -2,7 +2,7 @@ import type { BottomTabBarProps } from "@react-navigation/bottom-tabs"
|
|||
import { getLabel } from "@react-navigation/elements"
|
||||
import { CommonActions, NavigationContext, NavigationRouteContext } from "@react-navigation/native"
|
||||
import type { FC } from "react"
|
||||
import { useContext, useEffect } from "react"
|
||||
import { memo, useContext, useEffect } from "react"
|
||||
import type { StyleProp, TextStyle } from "react-native"
|
||||
import { Platform, Pressable, StyleSheet, View } from "react-native"
|
||||
import Animated, {
|
||||
|
|
@ -127,19 +127,18 @@ export const Tabbar: FC<BottomTabBarProps> = (props) => {
|
|||
/>
|
||||
)
|
||||
}
|
||||
const scene = { route, focused }
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider key={route.key} value={descriptors[route.key]?.navigation}>
|
||||
<NavigationRouteContext.Provider value={route}>
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
className="flex-1 flex-col items-center justify-center"
|
||||
>
|
||||
{renderIcon(scene)}
|
||||
{renderLabel(scene)}
|
||||
</Pressable>
|
||||
</NavigationRouteContext.Provider>
|
||||
</NavigationContext.Provider>
|
||||
<TabItem
|
||||
key={route.key}
|
||||
route={route}
|
||||
focused={focused}
|
||||
descriptors={descriptors}
|
||||
onPress={onPress}
|
||||
originalRenderIcon={renderIcon}
|
||||
originalRenderLabel={renderLabel}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
|
|
@ -260,3 +259,57 @@ const TabBarBackground = () => {
|
|||
const borderColor = useColor("opaqueSeparator")
|
||||
return <AnimatedThemedBlurView style={[styles.blurEffect, animatedStyle, { borderColor }]} />
|
||||
}
|
||||
|
||||
const TabItem = memo(
|
||||
// eslint-disable-next-line @eslint-react/no-unstable-context-value
|
||||
({
|
||||
route,
|
||||
focused,
|
||||
descriptors,
|
||||
onPress,
|
||||
originalRenderIcon,
|
||||
originalRenderLabel,
|
||||
accessibilityLabel,
|
||||
}: {
|
||||
route: any
|
||||
focused: boolean
|
||||
descriptors: any
|
||||
onPress: () => void
|
||||
originalRenderIcon: (scene: { route: any; focused: boolean }) => React.ReactNode
|
||||
originalRenderLabel: (scene: { route: any; focused: boolean }) => React.ReactNode
|
||||
accessibilityLabel?: string
|
||||
}) => {
|
||||
const pressed = useSharedValue(0)
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{ scale: 1 - 0.15 * pressed.value }],
|
||||
}
|
||||
})
|
||||
|
||||
const scene = { route, focused }
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider value={descriptors[route.key]?.navigation}>
|
||||
<NavigationRouteContext.Provider value={route}>
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
onPressIn={() => {
|
||||
pressed.value = withSpring(1, { damping: 15, stiffness: 150 })
|
||||
}}
|
||||
onPressOut={() => {
|
||||
pressed.value = withSpring(0, { damping: 15, stiffness: 150 })
|
||||
}}
|
||||
className="flex-1 flex-col items-center justify-center"
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole="button"
|
||||
accessibilityState={{ selected: focused }}
|
||||
>
|
||||
<Animated.View style={animatedStyle}>{originalRenderIcon(scene)}</Animated.View>
|
||||
{originalRenderLabel(scene)}
|
||||
</Pressable>
|
||||
</NavigationRouteContext.Provider>
|
||||
</NavigationContext.Provider>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import type { FlashList } from "@shopify/flash-list"
|
||||
import { requireNativeModule } from "expo"
|
||||
import type { RefObject } from "react"
|
||||
import { useCallback, useContext, useEffect, useRef } from "react"
|
||||
import type { FlatList, ScrollView, View } from "react-native"
|
||||
import type { FlatList, ScrollView } from "react-native"
|
||||
import { findNodeHandle, Platform } from "react-native"
|
||||
|
||||
import PlaterScreen from "@/src/screens/player"
|
||||
import { performNativeScrollToTop } from "@/src/lib/native"
|
||||
|
||||
import {
|
||||
AttachNavigationScrollViewContext,
|
||||
|
|
@ -28,8 +26,10 @@ export const useNavigationScrollToTop = (
|
|||
|
||||
if (Platform.OS === "ios") {
|
||||
const reactTag = findNodeHandle($scroller)
|
||||
requireNativeModule("Helper").scrollToTop(reactTag)
|
||||
return
|
||||
if (reactTag) {
|
||||
performNativeScrollToTop(reactTag)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ("scrollTo" in $scroller) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { requireNativeModule } from "expo"
|
|||
interface NativeModule {
|
||||
openLink: (url: string) => void
|
||||
previewImage: (images: string[]) => void
|
||||
scrollToTop: (reactTag: number) => void
|
||||
}
|
||||
const nativeModule = requireNativeModule("Helper") as NativeModule
|
||||
export const openLink = (url: string) => {
|
||||
|
|
@ -11,3 +12,7 @@ export const openLink = (url: string) => {
|
|||
export const quickLookImage = (images: string[]) => {
|
||||
nativeModule.previewImage(images)
|
||||
}
|
||||
|
||||
export const performNativeScrollToTop = (reactTag: number) => {
|
||||
nativeModule.scrollToTop(reactTag)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,3 +5,7 @@ export const openLink = (url: string) => {
|
|||
}
|
||||
|
||||
export const quickLookImage = (_images: string[]) => {}
|
||||
|
||||
export const performNativeScrollToTop = (_reactTag: number) => {
|
||||
throw new Error("performNativeScrollToTop is not supported on this platform")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue