diff --git a/src/components/NumberTicker.tsx b/src/components/NumberTicker.tsx new file mode 100644 index 00000000000..3dfb6cf3509 --- /dev/null +++ b/src/components/NumberTicker.tsx @@ -0,0 +1,114 @@ +import * as React from 'react' +import { Animated, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native' +import { typeScale } from 'src/styles/fonts' + +interface CommonProps { + textHeight: number + textStyles?: StyleProp + animationDuration?: number +} +interface Props extends CommonProps { + finalValue: string + testID?: string +} + +interface TickProps extends CommonProps { + startValue: number + endValue: number +} + +interface TickTextProps extends CommonProps { + value: string +} + +const numberRange = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + +function TickText({ value, textHeight, textStyles }: TickTextProps) { + return ( + + {value} + + ) +} + +function Tick({ startValue, endValue, textHeight, textStyles, animationDuration }: TickProps) { + const animatedValue = new Animated.Value(startValue * textHeight * -1) + const transformStyle = { transform: [{ translateY: animatedValue }] } + const duration = animationDuration ?? 1300 + + Animated.timing(animatedValue, { + toValue: endValue * textHeight * -1, + duration, + useNativeDriver: true, + }).start() + + return ( + + {numberRange.map((number, index) => { + return ( + + ) + })} + + ) +} + +export default function NumberTicker({ + finalValue, + textStyles, + textHeight, + animationDuration, + testID, +}: Props) { + const finalValueArray = finalValue.toString().split('') + + // For the startValueArray, map over each character in the finalValueArray to + // replace digits with random digits, do not change non-digit characters (e.g. + // decimal separator) + const startValueArray = finalValueArray.map((char) => { + return char.match(/\d/) ? Math.floor(Math.random() * 10).toString() : char + }) + + return ( + + {finalValueArray.map((value, index) => { + // If the character is not a digit, render it as a static text element + if (!value.match(/\d/)) { + return ( + + ) + } + + const endValue = parseInt(value, 10) + const startValue = parseInt(startValueArray[index], 10) + return ( + + ) + })} + + ) +} + +const styles = StyleSheet.create({ + container: { + overflow: 'hidden', + flexDirection: 'row', + // This negative gap is a hack to bring the numbers closer together, + // otherwise they feel unnatural and far apart + gap: -2, + }, + tickText: { + alignItems: 'center', + justifyContent: 'center', + }, + text: { + ...typeScale.displaySmall, + }, +}) diff --git a/src/points/PointsHome.test.tsx b/src/points/PointsHome.test.tsx index 2179c146719..4631cab4835 100644 --- a/src/points/PointsHome.test.tsx +++ b/src/points/PointsHome.test.tsx @@ -108,7 +108,7 @@ describe(PointsHome, () => { const { getByTestId, getByText, queryByText } = renderPointsHome('success', {}) expect(getByText('points.title')).toBeTruthy() - expect(getByText('50')).toBeTruthy() // balance + expect(getByTestId('PointsBalance')).toBeTruthy() // balance is animated so we cannot properly test the value programatically expect(getByTestId('PointsActivityButton')).toBeTruthy() expect(getByText('points.noActivities.title')).toBeTruthy() expect(getByText('points.noActivities.body')).toBeTruthy() diff --git a/src/points/PointsHome.tsx b/src/points/PointsHome.tsx index 0a7bff1495d..bcc0a6dfa54 100644 --- a/src/points/PointsHome.tsx +++ b/src/points/PointsHome.tsx @@ -11,6 +11,7 @@ import { BottomSheetParams, PointsActivityId } from 'src/points/types' import ValoraAnalytics from 'src/analytics/ValoraAnalytics' import { PointsEvents } from 'src/analytics/Events' import InLineNotification, { NotificationVariant } from 'src/components/InLineNotification' +import NumberTicker from 'src/components/NumberTicker' import CustomHeader from 'src/components/header/CustomHeader' import PointsHistoryBottomSheet from 'src/points/PointsHistoryBottomSheet' import AttentionIcon from 'src/icons/Attention' @@ -36,7 +37,7 @@ export default function PointsHome({ route, navigation }: Props) { const pointsConfigStatus = useSelector(pointsConfigStatusSelector) // TODO: Use real points balance - const pointsBalance = 50 + const pointsBalance = 562 const historyBottomSheetRef = useRef(null) const activityCardBottomSheetRef = useRef(null) @@ -121,7 +122,12 @@ export default function PointsHome({ route, navigation }: Props) { /> - {pointsBalance} +