Skip to content

Commit f17446d

Browse files
committed
feat: enhance tab layout and settings screen functionality
- Added screen listeners for tab press and transition start to manage opacity in TabLayout. - Introduced a context provider for tracking focus state in SettingsX. - Refactored opacity handling in Settings component to improve scroll behavior and UI responsiveness. This update improves user experience by ensuring the tab bar opacity responds correctly to user interactions and screen focus. Signed-off-by: Innei <tukon479@gmail.com>
1 parent 8ab6835 commit f17446d

File tree

2 files changed

+50
-20
lines changed

2 files changed

+50
-20
lines changed

apps/mobile/src/screens/(stack)/(tabs)/_layout.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ export default function TabLayout() {
3535
<FeedDrawer>
3636
<TabBarBackgroundContext.Provider value={useMemo(() => ({ opacity }), [opacity])}>
3737
<Tabs
38+
screenListeners={{
39+
tabPress: () => {
40+
opacity.value = 1
41+
},
42+
transitionStart: () => {
43+
opacity.value = 1
44+
},
45+
}}
3846
screenOptions={{
3947
tabBarBackground: TabBarBackground,
4048
tabBarStyle: {

apps/mobile/src/screens/(stack)/(tabs)/settings.tsx

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs"
2+
import { useIsFocused } from "@react-navigation/native"
23
import { createNativeStackNavigator } from "@react-navigation/native-stack"
3-
import { useContext, useEffect } from "react"
4+
import { createContext, useCallback, useContext, useEffect, useRef } from "react"
45
import type { NativeScrollEvent, NativeSyntheticEvent } from "react-native"
5-
import { ScrollView, useAnimatedValue } from "react-native"
6+
import { findNodeHandle, ScrollView, UIManager, useAnimatedValue } from "react-native"
67
import { withTiming } from "react-native-reanimated"
78
import { useSafeAreaInsets } from "react-native-safe-area-context"
89
import { useEventCallback } from "usehooks-ts"
@@ -13,33 +14,29 @@ import { SettingsList } from "@/src/modules/settings/SettingsList"
1314
import { UserHeaderBanner } from "@/src/modules/settings/UserHeaderBanner"
1415

1516
const Stack = createNativeStackNavigator()
17+
const OutIsFocused = createContext(false)
1618
export default function SettingsX() {
19+
const isFocused = useIsFocused()
20+
1721
return (
18-
<Stack.Navigator initialRouteName="Settings">
19-
<Stack.Screen name="Settings" component={Settings} options={{ headerShown: false }} />
20-
{SettingRoutes(Stack)}
21-
</Stack.Navigator>
22+
<OutIsFocused.Provider value={isFocused}>
23+
<Stack.Navigator initialRouteName="Settings">
24+
<Stack.Screen name="Settings" component={Settings} options={{ headerShown: false }} />
25+
{SettingRoutes(Stack)}
26+
</Stack.Navigator>
27+
</OutIsFocused.Provider>
2228
)
2329
}
2430

2531
function Settings() {
2632
const insets = useSafeAreaInsets()
33+
const isFocused = useContext(OutIsFocused)
2734
const { opacity } = useContext(TabBarBackgroundContext)
2835
const tabBarHeight = useBottomTabBarHeight()
29-
useEffect(() => {
30-
opacity.value = 1
31-
return () => {
32-
opacity.value = 1
33-
}
34-
}, [opacity])
35-
36-
const animatedScrollY = useAnimatedValue(0)
37-
const handleScroll = useEventCallback(
38-
({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
39-
const { contentOffset, contentSize, layoutMeasurement } = nativeEvent
40-
41-
const distanceFromBottom = contentSize.height - layoutMeasurement.height - contentOffset.y
4236

37+
const calculateOpacity = useCallback(
38+
(contentHeight: number, viewportHeight: number, scrollY: number) => {
39+
const distanceFromBottom = contentHeight - viewportHeight - scrollY
4340
const fadeThreshold = 50
4441

4542
if (distanceFromBottom <= fadeThreshold) {
@@ -48,13 +45,38 @@ function Settings() {
4845
} else {
4946
opacity.value = withTiming(1, { duration: 150 })
5047
}
51-
animatedScrollY.setValue(nativeEvent.contentOffset.y)
5248
},
49+
[opacity],
5350
)
51+
52+
useEffect(() => {
53+
if (!isFocused) return
54+
const scrollView = scrollRef.current
55+
if (scrollView) {
56+
const node = findNodeHandle(scrollView)
57+
if (node) {
58+
UIManager.measure(node, (x, y, width, height) => {
59+
calculateOpacity(height, height, 0)
60+
})
61+
}
62+
}
63+
}, [opacity, isFocused, calculateOpacity])
64+
65+
const animatedScrollY = useAnimatedValue(0)
66+
const handleScroll = useEventCallback(
67+
({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
68+
const { contentOffset, contentSize, layoutMeasurement } = nativeEvent
69+
calculateOpacity(contentSize.height, layoutMeasurement.height, contentOffset.y)
70+
animatedScrollY.setValue(contentOffset.y)
71+
},
72+
)
73+
74+
const scrollRef = useRef<ScrollView>(null)
5475
return (
5576
<ScrollView
5677
scrollEventThrottle={16}
5778
onScroll={handleScroll}
79+
ref={scrollRef}
5880
style={{ paddingTop: insets.top }}
5981
className="bg-system-background flex-1"
6082
contentContainerStyle={{ paddingBottom: insets.bottom + tabBarHeight }}

0 commit comments

Comments
 (0)