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

Swaps V2 Integration #3013

Merged
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
2 changes: 1 addition & 1 deletion app/components/Nav/Main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ const Main = props => {
// if destination address is metaswap contract
if (
to &&
(to === swapsUtils.getSwapsContractAddress(props.chainId) ||
(swapsUtils.isValidContractAddress(props.chainId, to) ||
(data &&
data.substr(0, 10) === APPROVE_FUNCTION_SIGNATURE &&
decodeApproveData(data).spenderAddress?.toLowerCase() ===
Expand Down
15 changes: 9 additions & 6 deletions app/components/UI/Swaps/QuotesView.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import { swapsTokensSelector } from '../../../reducers/swaps';
import { decGWEIToHexWEI } from '../../../util/conversions';
import FadeAnimationView from '../FadeAnimationView';

const POLLING_INTERVAL = AppConstants.SWAPS.POLLING_INTERVAL;
const POLLING_INTERVAL = 30000;
const SLIPPAGE_BUCKETS = {
MEDIUM: AppConstants.GAS_OPTIONS.MEDIUM,
HIGH: AppConstants.GAS_OPTIONS.HIGH
Expand Down Expand Up @@ -1137,6 +1137,7 @@ function SwapsQuotesView({
finish={shouldFinishFirstLoad}
onAnimationEnd={handleAnimationEnd}
aggregatorMetadata={aggregatorMetadata}
headPan={false}
/>
</ScreenView>
);
Expand Down Expand Up @@ -1358,11 +1359,13 @@ function SwapsQuotesView({
<QuotesSummary.HeaderText bold>
{isSaving ? strings('swaps.savings') : strings('swaps.using_best_quote')}
</QuotesSummary.HeaderText>
<TouchableOpacity onPress={handleOpenQuotesModal} disabled={isInFetch}>
<QuotesSummary.HeaderText small>
{strings('swaps.view_details')} →
</QuotesSummary.HeaderText>
</TouchableOpacity>
{allQuotes.length > 1 && (
<TouchableOpacity onPress={handleOpenQuotesModal} disabled={isInFetch}>
<QuotesSummary.HeaderText small>
{strings('swaps.view_details')} →
</QuotesSummary.HeaderText>
</TouchableOpacity>
)}
</QuotesSummary.Header>
<QuotesSummary.Body>
<View style={styles.quotesRow}>
Expand Down
255 changes: 139 additions & 116 deletions app/components/UI/Swaps/components/LoadingAnimation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function round(value, decimals) {
return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
}

function LoadingAnimation({ finish, onAnimationEnd, aggregatorMetadata }) {
function LoadingAnimation({ finish, onAnimationEnd, aggregatorMetadata, headPan = true }) {
const [metadata, setMetadata] = useState([]);
const [shouldStart, setShouldStart] = useState(false);
const [hasStarted, setHasStarted] = useState(false);
Expand Down Expand Up @@ -135,115 +135,125 @@ function LoadingAnimation({ finish, onAnimationEnd, aggregatorMetadata }) {
*/
const positions = useMemo(
() =>
metadata.reduce((acc, curr, index) => {
// Vertical position is random and is in range [-0.6, 0.6]
// making the head not look so steep up/down
const y = Math.random() * 0.6 * (Math.random() < 0.5 ? -1 : 1);
const isNegativeY = y < 0;

// Horizontal position will be to the left or right depending 70% on the
// index, this ensures the head moves from left to right in these cases
// Otherwise is random.
const isNegativeX = Math.random() < 0.7 ? index % 2 === 0 : Math.random() < 0.5;
const x = isNegativeX ? -1 : 1;

// Head pan values, horizontal pan value is randomly changed by [-0.4,0.4]
// so the head rotates differently some times.
const panRadioX = (x + (0.8 * Math.random() - 0.8)) * PAN_RADIO;
const panRadioY = y * PAN_RADIO;

// Icons positions are compensated by their size according to the position
const radioY = AGG_RADIO * y - (isNegativeY ? 40 : 0);
// Horizontal position depends on vertical position, making the logo sit
// in a radius from origin and not always same horizontal distance
const radioX = Math.sqrt(1 - Math.pow(y, 2)) * x * AGG_RADIO - (isNegativeX ? 95 : 0);

return {
...acc,
[curr.key]: [panRadioX, panRadioY, radioX, radioY]
};
}, {}),
[metadata]
headPan
? metadata.reduce((acc, curr, index) => {
// Vertical position is random and is in range [-0.6, 0.6]
// making the head not look so steep up/down
const y = Math.random() * 0.6 * (Math.random() < 0.5 ? -1 : 1);
const isNegativeY = y < 0;

// Horizontal position will be to the left or right depending 70% on the
// index, this ensures the head moves from left to right in these cases
// Otherwise is random.
const isNegativeX = Math.random() < 0.7 ? index % 2 === 0 : Math.random() < 0.5;
const x = isNegativeX ? -1 : 1;

// Head pan values, horizontal pan value is randomly changed by [-0.4,0.4]
// so the head rotates differently some times.
const panRadioX = (x + (0.8 * Math.random() - 0.8)) * PAN_RADIO;
const panRadioY = y * PAN_RADIO;

// Icons positions are compensated by their size according to the position
const radioY = AGG_RADIO * y - (isNegativeY ? 40 : 0);
// Horizontal position depends on vertical position, making the logo sit
// in a radius from origin and not always same horizontal distance
const radioX = Math.sqrt(1 - Math.pow(y, 2)) * x * AGG_RADIO - (isNegativeX ? 95 : 0);

return {
...acc,
[curr.key]: [panRadioX, panRadioY, radioX, radioY]
};
// eslint-disable-next-line no-mixed-spaces-and-tabs
}, {})
: {},
[metadata, headPan]
);

// The opacity for each of the icons
const opacities = useMemo(
() =>
metadata.reduce(
(acc, curr) => ({
...acc,
[curr.key]: new Animated.Value(0)
}),
{}
),
[metadata]
headPan
? metadata.reduce(
(acc, curr) => ({
...acc,
[curr.key]: new Animated.Value(0)
}),
{}
// eslint-disable-next-line no-mixed-spaces-and-tabs
)
: {},
[metadata, headPan]
);

// The sequence for each aggregator
const animationSequence = useMemo(
() => [
// Animated.delay(INITIAL_DELAY),
...metadata.reduce(
(acc, cur, index, array) => [
...acc,
// Time to delay next iteration, this is the amount of time the head looks at the icon
Animated.delay(index > 0 ? DELAY : 0),
// Track the current index of the array
Animated.timing(currentQuoteIndexValue, {
toValue: index,
duration: 0,
useNativeDriver: true
}),
Animated.parallel([
// If is not the first aggregator, reduce previous aggregator opacity to 1
index > 0 &&
Animated.timing(opacities[array[index - 1].key], {
() =>
headPan
? [
// Animated.delay(INITIAL_DELAY),
...metadata.reduce(
(acc, cur, index, array) => [
...acc,
// Time to delay next iteration, this is the amount of time the head looks at the icon
Animated.delay(index > 0 ? DELAY : 0),
// Track the current index of the array
Animated.timing(currentQuoteIndexValue, {
toValue: index,
duration: 0,
useNativeDriver: true
}),
Animated.parallel([
// If is not the first aggregator, reduce previous aggregator opacity to 1
index > 0 &&
Animated.timing(opacities[array[index - 1].key], {
toValue: 0,
duration: PAN_DURATION,
useNativeDriver: true
}),
// Set current aggregator opacity to 1
Animated.timing(opacities[cur.key], {
toValue: 1,
duration: PAN_DURATION,
useNativeDriver: true
}),
// Update progress bar given the current index
Animated.timing(progressValue, {
toValue: (FINALIZING_PERCENTAGE / array.length) * (index + 1),
duration: PAN_DURATION,
useNativeDriver: false
}),
// Make the fox head pan to the aggregator position
!Device.isAndroid() &&
Animated.timing(foxHeadPan, {
toValue: { x: positions[cur.key][0], y: positions[cur.key][1] },
duration: PAN_DURATION,
useNativeDriver: true
})
])
],
[]
),
// Final animation of the sequence
Animated.delay(DELAY),
Animated.parallel([
// Set last aggregator icon opacity to 0
Animated.timing(opacities[([...metadata].pop()?.key)], {
toValue: 0,
duration: PAN_DURATION,
useNativeDriver: true
}),
// Set current aggregator opacity to 1
Animated.timing(opacities[cur.key], {
toValue: 1,
duration: PAN_DURATION,
useNativeDriver: true
}),
// Update progress bar given the current index
Animated.timing(progressValue, {
toValue: (FINALIZING_PERCENTAGE / array.length) * (index + 1),
duration: PAN_DURATION,
useNativeDriver: false
}),
// Make the fox head pan to the aggregator position
!Device.isAndroid() &&
Animated.timing(foxHeadPan, {
toValue: { x: positions[cur.key][0], y: positions[cur.key][1] },
duration: PAN_DURATION,
useNativeDriver: true
})
])
],
[]
),
// Final animation of the sequence
Animated.delay(DELAY),
Animated.parallel([
// Set last aggregator icon opacity to 0
Animated.timing(opacities[([...metadata].pop()?.key)], {
toValue: 0,
duration: PAN_DURATION,
useNativeDriver: true
}),
// Reset to fox head to origing
!Device.isAndroid() &&
Animated.timing(foxHeadPan, {
toValue: { x: 0, y: 0 },
duration: PAN_DURATION,
useNativeDriver: true
})
])
],
[currentQuoteIndexValue, foxHeadPan, metadata, opacities, positions, progressValue]
// Reset to fox head to origing
!Device.isAndroid() &&
Animated.timing(foxHeadPan, {
toValue: { x: 0, y: 0 },
duration: PAN_DURATION,
useNativeDriver: true
})
])
// eslint-disable-next-line no-mixed-spaces-and-tabs
]
: [],
[currentQuoteIndexValue, foxHeadPan, headPan, metadata, opacities, positions, progressValue]
);

const startAnimation = useCallback(() => {
Expand Down Expand Up @@ -360,25 +370,33 @@ function LoadingAnimation({ finish, onAnimationEnd, aggregatorMetadata }) {
return (
<View style={styles.screen}>
<View style={styles.content}>
<Text small centered>
{hasStarted ? (
<>
{strings('swaps.quote')}{' '}
<Text reset bold>
{currentQuoteIndex + 1} {strings('swaps.of')} {metadata?.length}
</Text>
</>
) : (
''
)}
</Text>
{!hasStarted && <Title centered>{strings('swaps.starting')}</Title>}
{hasStarted && !hasFinished && (
<Title centered>
{strings('swaps.checking')} {metadata[currentQuoteIndex]?.title}...
</Title>
{headPan ? (
<>
<Text small centered>
{hasStarted ? (
<>
{strings('swaps.quote')}{' '}
<Text reset bold>
{currentQuoteIndex + 1} {strings('swaps.of')} {metadata?.length}
</Text>
</>
) : (
''
)}
</Text>
{!hasStarted && <Title centered>{strings('swaps.starting')}</Title>}
{hasStarted && !hasFinished && (
<Title centered>
{strings('swaps.checking')} {metadata[currentQuoteIndex]?.title}...
</Title>
)}
{hasFinished && <Title centered>{strings('swaps.finalizing')}</Title>}
</>
) : (
<>
<Title centered>{strings('swaps.fetching_quotes')}</Title>
</>
)}
{hasFinished && <Title centered>{strings('swaps.finalizing')}</Title>}

<View style={styles.progressWrapper}>
<Animated.View style={[styles.progressBar, { width: progressWidth }]} />
Expand All @@ -392,6 +410,7 @@ function LoadingAnimation({ finish, onAnimationEnd, aggregatorMetadata }) {
renderLoading={() => null}
/>
{renderLogos &&
headPan &&
metadata &&
metadata.map(agg => (
<Animated.View
Expand Down Expand Up @@ -429,7 +448,11 @@ LoadingAnimation.propTypes = {
/**
* Aggregator metada from Swaps controller API
*/
aggregatorMetadata: PropTypes.object
aggregatorMetadata: PropTypes.object,
/**
* Wether to show head panning animation with aggregators logos
*/
headPan: PropTypes.bool
};

export default LoadingAnimation;
Loading