fix(app): replace tab opacity=0 flash with proper slide+fade cross-fade
Use prevTab + Animated translateX/opacity driven by progress sharedValue. Set opacity to 0 only inside a withTiming, never as initial value.
This commit is contained in:
1 parent
e16d31318b
commit
2e527fbf5f
1 file changed
+31
-9
@@ -18,6 +18,7 @@ import {
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import Animated, {
|
||||
Easing,
|
||||
runOnJS,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
@@ -49,17 +50,38 @@ export function FightRateApp() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const monitor = useHeartRateMonitor();
|
||||
const [activeTab, setActiveTab] = useState<TabKey>('capture');
|
||||
const opacity = useSharedValue(1);
|
||||
const [prevTab, setPrevTab] = useState<TabKey>('capture');
|
||||
const progress = useSharedValue(1);
|
||||
const direction = useSharedValue(1);
|
||||
|
||||
useEffect(() => {
|
||||
opacity.value = 0;
|
||||
opacity.value = withTiming(1, {
|
||||
duration: motion.duration.fast,
|
||||
easing: Easing.out(Easing.quad),
|
||||
});
|
||||
}, [activeTab, opacity]);
|
||||
if (activeTab === prevTab) {
|
||||
return;
|
||||
}
|
||||
const tabOrder: TabKey[] = ['capture', 'history', 'sensor', 'insights'];
|
||||
direction.value =
|
||||
tabOrder.indexOf(activeTab) > tabOrder.indexOf(prevTab) ? 1 : -1;
|
||||
progress.value = 0;
|
||||
progress.value = withTiming(
|
||||
1,
|
||||
{
|
||||
duration: motion.duration.fast,
|
||||
easing: Easing.out(Easing.quad),
|
||||
},
|
||||
(finished) => {
|
||||
if (finished) {
|
||||
runOnJS(setPrevTab)(activeTab);
|
||||
}
|
||||
},
|
||||
);
|
||||
}, [activeTab, prevTab, progress, direction]);
|
||||
|
||||
const fadeStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
|
||||
const enterStyle = useAnimatedStyle(() => ({
|
||||
opacity: progress.value,
|
||||
transform: [
|
||||
{ translateX: (1 - progress.value) * 24 * direction.value },
|
||||
],
|
||||
}));
|
||||
|
||||
const onCaptureAction = useCallback(async () => {
|
||||
if (monitor.isCapturing) {
|
||||
@@ -117,7 +139,7 @@ export function FightRateApp() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Animated.View style={fadeStyle} key={`tab-${activeTab}`}>
|
||||
<Animated.View key={`tab-${prevTab}`} style={enterStyle}>
|
||||
{activeTab === 'capture' ? (
|
||||
<CaptureScreen
|
||||
monitor={monitor}
|
||||
|
||||
Reference in new issue
Block a user