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:
fightrate-bot committed 2026-06-16 17:24:59 -03:00
1 parent e16d31318b
commit 2e527fbf5f
1 file changed
+31 -9
+31 -9
View File
@@ -18,6 +18,7 @@ import {
import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Animated, { import Animated, {
Easing, Easing,
runOnJS,
useAnimatedStyle, useAnimatedStyle,
useSharedValue, useSharedValue,
withTiming, withTiming,
@@ -49,17 +50,38 @@ export function FightRateApp() {
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const monitor = useHeartRateMonitor(); const monitor = useHeartRateMonitor();
const [activeTab, setActiveTab] = useState<TabKey>('capture'); 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(() => { useEffect(() => {
opacity.value = 0; if (activeTab === prevTab) {
opacity.value = withTiming(1, { return;
duration: motion.duration.fast, }
easing: Easing.out(Easing.quad), const tabOrder: TabKey[] = ['capture', 'history', 'sensor', 'insights'];
}); direction.value =
}, [activeTab, opacity]); 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 () => { const onCaptureAction = useCallback(async () => {
if (monitor.isCapturing) { if (monitor.isCapturing) {
@@ -117,7 +139,7 @@ export function FightRateApp() {
</View> </View>
</View> </View>
<Animated.View style={fadeStyle} key={`tab-${activeTab}`}> <Animated.View key={`tab-${prevTab}`} style={enterStyle}>
{activeTab === 'capture' ? ( {activeTab === 'capture' ? (
<CaptureScreen <CaptureScreen
monitor={monitor} monitor={monitor}