Skip to content

Commit

Permalink
Merge pull request #53655 from callstack-internal/JKobrynski/fix/5302…
Browse files Browse the repository at this point in the history
…7-transaction-violation-flash
  • Loading branch information
blimpich authored Dec 23, 2024
2 parents 5c8a21f + 3aa6c45 commit 126d92a
Show file tree
Hide file tree
Showing 12 changed files with 417 additions and 196 deletions.
10 changes: 0 additions & 10 deletions src/components/TabSelector/TabSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ type IconAndTitle = {

function getIconAndTitle(route: string, translate: LocaleContextProps['translate']): IconAndTitle {
switch (route) {
case CONST.DEBUG.DETAILS:
return {icon: Expensicons.Info, title: translate('debug.details')};
case CONST.DEBUG.JSON:
return {icon: Expensicons.Eye, title: translate('debug.JSON')};
case CONST.DEBUG.REPORT_ACTIONS:
return {icon: Expensicons.Document, title: translate('debug.reportActions')};
case CONST.DEBUG.REPORT_ACTION_PREVIEW:
return {icon: Expensicons.Document, title: translate('debug.reportActionPreview')};
case CONST.DEBUG.TRANSACTION_VIOLATIONS:
return {icon: Expensicons.Exclamation, title: translate('debug.violations')};
case CONST.TAB_REQUEST.MANUAL:
return {icon: Expensicons.Pencil, title: translate('tabSelector.manual')};
case CONST.TAB_REQUEST.SCAN:
Expand Down
19 changes: 12 additions & 7 deletions src/components/TabSelector/getBackground/index.native.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import type {Animated} from 'react-native';
import type GetBackgroudColor from './types';

const getBackgroundColor: GetBackgroudColor = ({routesLength, tabIndex, affectedTabs, theme, position}) => {
const getBackgroundColor: GetBackgroudColor = ({routesLength, tabIndex, affectedTabs, theme, position, isActive}) => {
if (routesLength > 1) {
const inputRange = Array.from({length: routesLength}, (v, i) => i);
return position?.interpolate({
inputRange,
outputRange: inputRange.map((i) => {
return affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG;
}),
}) as unknown as Animated.AnimatedInterpolation<string>;

if (position) {
return position.interpolate({
inputRange,
outputRange: inputRange.map((i) => {
return affectedTabs.includes(tabIndex) && i === tabIndex ? theme.border : theme.appBG;
}),
}) as unknown as Animated.AnimatedInterpolation<string>;
}

return affectedTabs.includes(tabIndex) && isActive ? theme.border : theme.appBG;
}
return theme.border;
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/TabSelector/getBackground/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type GetBackgroudColorConfig = {
/**
* The animated position interpolation.
*/
position: Animated.AnimatedInterpolation<number>;
position: Animated.AnimatedInterpolation<number> | undefined;

/**
* Whether the tab is active.
Expand Down
14 changes: 9 additions & 5 deletions src/components/TabSelector/getOpacity/index.native.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import type GetOpacity from './types';

const getOpacity: GetOpacity = ({routesLength, tabIndex, active, affectedTabs, position}) => {
const getOpacity: GetOpacity = ({routesLength, tabIndex, active, affectedTabs, position, isActive}) => {
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)),
});
if (position) {
return position.interpolate({
inputRange,
outputRange: inputRange.map((i) => (affectedTabs.includes(tabIndex) && i === tabIndex ? activeValue : inactiveValue)),
});
}

return affectedTabs.includes(tabIndex) && isActive ? activeValue : inactiveValue;
}
return activeValue;
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/TabSelector/getOpacity/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type GetOpacityConfig = {
/**
* Scene's position, value which we would like to interpolate.
*/
position: Animated.AnimatedInterpolation<number>;
position: Animated.AnimatedInterpolation<number> | undefined;

/**
* Whether the tab is active.
Expand Down
152 changes: 152 additions & 0 deletions src/libs/Navigation/DebugTabNavigator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import type {EventMapCore, NavigationProp, NavigationState} from '@react-navigation/native';
import {useNavigation} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import React, {useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
import * as Expensicons from '@components/Icon/Expensicons';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import getBackgroundColor from '@components/TabSelector/getBackground';
import getOpacity from '@components/TabSelector/getOpacity';
import TabSelectorItem from '@components/TabSelector/TabSelectorItem';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import type IconAsset from '@src/types/utils/IconAsset';

type IconAndTitle = {
icon: IconAsset;
title: string;
};

function getIconAndTitle(route: string, translate: LocaleContextProps['translate']): IconAndTitle {
switch (route) {
case CONST.DEBUG.DETAILS:
return {icon: Expensicons.Info, title: translate('debug.details')};
case CONST.DEBUG.JSON:
return {icon: Expensicons.Eye, title: translate('debug.JSON')};
case CONST.DEBUG.REPORT_ACTIONS:
return {icon: Expensicons.Document, title: translate('debug.reportActions')};
case CONST.DEBUG.REPORT_ACTION_PREVIEW:
return {icon: Expensicons.Document, title: translate('debug.reportActionPreview')};
case CONST.DEBUG.TRANSACTION_VIOLATIONS:
return {icon: Expensicons.Exclamation, title: translate('debug.violations')};
default:
throw new Error(`Route ${route} has no icon nor title set.`);
}
}

const StackNavigator = createStackNavigator();

type DebugTabNavigatorRoute = {
name: string;
component: () => React.ReactNode;
};

type DebugTabNavigatorRoutes = DebugTabNavigatorRoute[];

type DebugTabNavigatorProps = {
id: string;
routes: DebugTabNavigatorRoutes;
};

function DebugTabNavigator({id, routes}: DebugTabNavigatorProps) {
const styles = useThemeStyles();
const theme = useTheme();
const navigation = useNavigation<NavigationProp<Record<string, void>>>();
const {translate} = useLocalize();
const [currentTab, setCurrentTab] = useState(routes.at(0)?.name);
const defaultAffectedAnimatedTabs = useMemo(() => Array.from({length: routes.length}, (v, i) => i), [routes.length]);
const [affectedAnimatedTabs, setAffectedAnimatedTabs] = useState(defaultAffectedAnimatedTabs);

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, currentTab]);

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

const onPress = () => {
navigation.navigate(route.name);
setCurrentTab(route.name);
};

return (
<TabSelectorItem
key={route.name}
icon={icon}
title={title}
onPress={onPress}
activeOpacity={activeOpacity}
inactiveOpacity={inactiveOpacity}
backgroundColor={backgroundColor}
isActive={isActive}
/>
);
})}
</View>
<StackNavigator.Navigator
id={id}
screenOptions={{
animationEnabled: false,
headerShown: false,
}}
screenListeners={{
state: (e) => {
const event = e as unknown as EventMapCore<NavigationState>['state'];
const state = event.data.state;
const routeNames = state.routeNames;
const newSelectedTab = state.routes.at(state.routes.length - 1)?.name;
if (currentTab === newSelectedTab || (currentTab && !routeNames.includes(currentTab))) {
return;
}
setCurrentTab(newSelectedTab);
},
}}
>
{routes.map((route) => (
<StackNavigator.Screen
key={route.name}
name={route.name}
component={route.component}
/>
))}
</StackNavigator.Navigator>
</>
);
}

export default DebugTabNavigator;

export type {DebugTabNavigatorRoutes};
15 changes: 7 additions & 8 deletions src/pages/Debug/Report/DebugReportActions.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from 'react';
import type {ListRenderItemInfo} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import FlatList from '@components/FlatList';
import {PressableWithFeedback} from '@components/Pressable';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
Expand All @@ -28,17 +26,20 @@ function DebugReportActions({reportID}: DebugReportActionsProps) {
canEvict: false,
selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, canUserPerformWriteAction, true),
});
const renderItem = ({item}: ListRenderItemInfo<ReportAction>) => (

const renderItem = (item: ReportAction, index: number) => (
<PressableWithFeedback
accessibilityLabel={translate('common.details')}
onPress={() => Navigation.navigate(ROUTES.DEBUG_REPORT_ACTION.getRoute(reportID, item.reportActionID))}
style={({pressed}) => [styles.flexRow, styles.justifyContentBetween, pressed && styles.hoveredComponentBG, styles.p4]}
hoverStyle={styles.hoveredComponentBG}
key={index}
>
<Text>{item.reportActionID}</Text>
<Text style={styles.textLabelSupporting}>{datetimeToCalendarTime(item.created, false, false)}</Text>
</PressableWithFeedback>
);

return (
<ScrollView style={styles.mv5}>
<Button
Expand All @@ -48,11 +49,9 @@ function DebugReportActions({reportID}: DebugReportActionsProps) {
onPress={() => Navigation.navigate(ROUTES.DEBUG_REPORT_ACTION_CREATE.getRoute(reportID))}
style={[styles.pb5, styles.ph3]}
/>
<FlatList
data={sortedAllReportActions}
renderItem={renderItem}
scrollEnabled={false}
/>
{/* This list was previously rendered as a FlatList, but it turned out that it caused the component to flash in some cases,
so it was replaced by this solution. */}
{sortedAllReportActions?.map((item, index) => renderItem(item, index))}
</ScrollView>
);
}
Expand Down
Loading

0 comments on commit 126d92a

Please sign in to comment.