Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CP Staging] Revert "Fix RequestMoney tabs animations" #33761

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 64 additions & 15 deletions src/components/TabSelector/TabSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {MaterialTopTabNavigationHelpers} from '@react-navigation/material-top-tabs/lib/typescript/src/types';
import {TabNavigationState} from '@react-navigation/native';
import React, {useMemo} from 'react';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import type {Animated} from 'react-native';
import {View} from 'react-native';
import * as Expensicons from '@components/Icon/Expensicons';
import {LocaleContextProps} from '@components/LocaleContextProvider';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import tabNavigatorAnimationEnabled from '@libs/Navigation/tabNavigatorAnimationEnabled';
import {RootStackParamList} from '@libs/Navigation/types';
import CONST from '@src/CONST';
import IconAsset from '@src/types/utils/IconAsset';
Expand All @@ -21,6 +22,9 @@ type TabSelectorProps = {

/* Callback fired when tab is pressed */
onTabPress?: (name: string) => void;

/* AnimatedValue for the position of the screen while swiping */
position: Animated.AnimatedInterpolation<number | string>;
};

type IconAndTitle = {
Expand All @@ -45,21 +49,66 @@ function getIconAndTitle(route: string, translate: LocaleContextProps['translate
}
}

function TabSelector({state, navigation, onTabPress}: TabSelectorProps) {
function getOpacity(position: Animated.AnimatedInterpolation<number>, routesLength: number, tabIndex: number, active: boolean, affectedTabs: number[]) {
const activeValue = active ? 1 : 0;
const inactiveValue = active ? 0 : 1;

if (routesLength > 1) {
const inputRange = Array.from({length: routesLength}, (v, i) => i);

return position.interpolate({
inputRange,
outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? activeValue : inactiveValue)),
});
}
return activeValue;
}

function TabSelector({state, navigation, onTabPress = () => {}, position}: TabSelectorProps) {
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
const defaultAffectedAnimatedTabs = useMemo(() => Array.from({length: state.routes.length}, (v, i) => i), [state.routes.length]);
const [affectedAnimatedTabs, setAffectedAnimatedTabs] = useState(defaultAffectedAnimatedTabs);

const tabs = useMemo(
() =>
state.routes.map((route, index) => {
const isFocused = index === state.index;
const getBackgroundColor = useCallback(
(routesLength: number, tabIndex: number, affectedTabs: number[]) => {
if (routesLength > 1) {
const inputRange = Array.from({length: routesLength}, (v, i) => i);

return position.interpolate({
inputRange,
outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG)),
});
}
return theme.border;
},
[theme, position],
);

useEffect(() => {
// It is required to wait transition end to reset affectedAnimatedTabs because tabs style is still animating during transition.
setTimeout(() => {
setAffectedAnimatedTabs(defaultAffectedAnimatedTabs);
}, CONST.ANIMATED_TRANSITION);
}, [defaultAffectedAnimatedTabs, state.index]);

return (
<View style={styles.tabSelector}>
{state.routes.map((route, index) => {
const activeOpacity = getOpacity(position, state.routes.length, index, true, affectedAnimatedTabs);
const inactiveOpacity = getOpacity(position, state.routes.length, index, false, affectedAnimatedTabs);
const backgroundColor = getBackgroundColor(state.routes.length, index, affectedAnimatedTabs);
const isActive = index === state.index;
const {icon, title} = getIconAndTitle(route.name, translate);

const onPress = () => {
if (isFocused) {
if (isActive) {
return;
}

setAffectedAnimatedTabs([state.index, index]);

const event = navigation.emit({
type: 'tabPress',
target: route.key,
Expand All @@ -71,7 +120,7 @@ function TabSelector({state, navigation, onTabPress}: TabSelectorProps) {
navigation.navigate({key: route.key, merge: true});
}

onTabPress?.(route.name);
onTabPress(route.name);
};

return (
Expand All @@ -80,15 +129,15 @@ function TabSelector({state, navigation, onTabPress}: TabSelectorProps) {
icon={icon}
title={title}
onPress={onPress}
isFocused={isFocused}
animationEnabled={tabNavigatorAnimationEnabled}
activeOpacity={activeOpacity}
inactiveOpacity={inactiveOpacity}
backgroundColor={backgroundColor}
isActive={isActive}
/>
);
}),
[navigation, onTabPress, state.index, state.routes, translate],
})}
</View>
);

return <View style={styles.tabSelector}>{tabs}</View>;
}

TabSelector.displayName = 'TabSelector';
Expand Down
60 changes: 17 additions & 43 deletions src/components/TabSelector/TabSelectorItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React, {useCallback, useEffect, useRef} from 'react';
import React from 'react';
import {Animated, StyleSheet} from 'react-native';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import IconAsset from '@src/types/utils/IconAsset';
import TabIcon from './TabIcon';
import TabLabel from './TabLabel';
Expand All @@ -18,45 +16,21 @@ type TabSelectorItemProps = {
/** Title of the tab */
title?: string;

/** Whether this tab is active */
isFocused?: boolean;

/** Whether animations should be skipped */
animationEnabled?: boolean;
};

function TabSelectorItem({icon, title = '', onPress = () => {}, isFocused = false, animationEnabled = true}: TabSelectorItemProps) {
const focusValueRef = useRef(new Animated.Value(isFocused ? 1 : 0));
const styles = useThemeStyles();
const theme = useTheme();

useEffect(() => {
const focusValue = isFocused ? 1 : 0;
/** Animated background color value for the tab button */
backgroundColor?: string | Animated.AnimatedInterpolation<string>;

if (animationEnabled) {
return Animated.timing(focusValueRef.current, {
toValue: focusValue,
duration: CONST.ANIMATED_TRANSITION,
useNativeDriver: true,
}).start();
}
/** Animated opacity value while the tab is in inactive state */
inactiveOpacity?: number | Animated.AnimatedInterpolation<number>;

focusValueRef.current.setValue(focusValue);
}, [animationEnabled, isFocused]);
/** Animated opacity value while the tab is in active state */
activeOpacity?: number | Animated.AnimatedInterpolation<number>;

const getBackgroundColorStyle = useCallback(
(hovered: boolean) => {
if (hovered && !isFocused) {
return {backgroundColor: theme.highlightBG};
}
return {backgroundColor: focusValueRef.current.interpolate({inputRange: [0, 1], outputRange: [theme.appBG, theme.border]})};
},
[theme, isFocused],
);

const activeOpacityValue = focusValueRef.current;
const inactiveOpacityValue = focusValueRef.current.interpolate({inputRange: [0, 1], outputRange: [1, 0]});
/** Whether this tab is active */
isActive?: boolean;
};

function TabSelectorItem({icon, title = '', onPress = () => {}, backgroundColor = '', activeOpacity = 0, inactiveOpacity = 1, isActive = false}: TabSelectorItemProps) {
const styles = useThemeStyles();
return (
<PressableWithFeedback
accessibilityLabel={title}
Expand All @@ -65,16 +39,16 @@ function TabSelectorItem({icon, title = '', onPress = () => {}, isFocused = fals
onPress={onPress}
>
{({hovered}) => (
<Animated.View style={[styles.tabSelectorButton, StyleSheet.absoluteFill, getBackgroundColorStyle(hovered)]}>
<Animated.View style={[styles.tabSelectorButton, StyleSheet.absoluteFill, styles.tabBackground(hovered, isActive, backgroundColor)]}>
<TabIcon
icon={icon}
activeOpacity={activeOpacityValue}
inactiveOpacity={inactiveOpacityValue}
activeOpacity={styles.tabOpacity(hovered, isActive, activeOpacity, inactiveOpacity).opacity}
inactiveOpacity={styles.tabOpacity(hovered, isActive, inactiveOpacity, activeOpacity).opacity}
/>
<TabLabel
title={title}
activeOpacity={activeOpacityValue}
inactiveOpacity={inactiveOpacityValue}
activeOpacity={styles.tabOpacity(hovered, isActive, activeOpacity, inactiveOpacity).opacity}
inactiveOpacity={styles.tabOpacity(hovered, isActive, inactiveOpacity, activeOpacity).opacity}
/>
</Animated.View>
)}
Expand Down
4 changes: 0 additions & 4 deletions src/libs/Navigation/OnyxTabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import {OnyxEntry} from 'react-native-onyx/lib/types';
import Tab from '@userActions/Tab';
import ONYXKEYS from '@src/ONYXKEYS';
import ChildrenProps from '@src/types/utils/ChildrenProps';
import tabNavigatorAnimationEnabled from './tabNavigatorAnimationEnabled';

const screenOptions = {animationEnabled: tabNavigatorAnimationEnabled};

type OnyxTabNavigatorOnyxProps = {
selectedTab: OnyxEntry<string>;
Expand Down Expand Up @@ -40,7 +37,6 @@ function OnyxTabNavigator({id, selectedTab = '', children, onTabSelected = () =>
{...rest}
id={id}
initialRouteName={selectedTab}
screenOptions={screenOptions}
backBehavior="initialRoute"
keyboardDismissMode="none"
screenListeners={{
Expand Down

This file was deleted.

3 changes: 0 additions & 3 deletions src/libs/Navigation/tabNavigatorAnimationEnabled/index.ts

This file was deleted.

8 changes: 7 additions & 1 deletion src/pages/iou/MoneyRequestSelectorPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,13 @@ function MoneyRequestSelectorPage(props) {
<OnyxTabNavigator
id={CONST.TAB.RECEIPT_TAB_ID}
selectedTab={props.selectedTab}
tabBar={TabSelector}
tabBar={({state, navigation, position}) => (
<TabSelector
state={state}
navigation={navigation}
position={position}
/>
)}
>
<TopTab.Screen
name={CONST.TAB_REQUEST.MANUAL}
Expand Down
14 changes: 14 additions & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3705,6 +3705,20 @@ const styles = (theme: ThemeColors) =>
color: isSelected ? theme.text : theme.textSupporting,
lineHeight: 14,
} satisfies TextStyle),

tabBackground: (hovered: boolean, isFocused: boolean, background: string | Animated.AnimatedInterpolation<string>) => ({
backgroundColor: hovered && !isFocused ? theme.highlightBG : background,
}),

tabOpacity: (
hovered: boolean,
isFocused: boolean,
activeOpacityValue: number | Animated.AnimatedInterpolation<number>,
inactiveOpacityValue: number | Animated.AnimatedInterpolation<number>,
) => ({
opacity: hovered && !isFocused ? inactiveOpacityValue : activeOpacityValue,
}),

overscrollSpacer: (backgroundColor: string, height: number) =>
({
backgroundColor,
Expand Down
Loading