import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
import {
Animated,
Easing,
Platform,
View,
ViewStyle,
type ActivityIndicatorProps,
} from 'react-native';
import AnimatedSpinner from './AnimatedSpinner';
import { StyleSheet } from 'react-native-unistyles';
import { getRegisteredMoleculesComponentStyles, registerComponentStyles } from '../../core';
export type Props = ActivityIndicatorProps & {
/**
* Whether to show the indicator or hide it.
*/
animating?: boolean;
/**
* The color of the spinner.
*/
color?: string;
/**
* Size of the indicator.
*/
size?: 'small' | 'large' | number;
/**
* Whether the indicator should hide when not animating.
*/
hidesWhenStopped?: boolean;
style?: ViewStyle;
};
const DURATION = 2400;
const mapIndicatorSize = (indicatorSize: 'small' | 'large' | number | undefined) => {
if (typeof indicatorSize === 'string') {
return indicatorSize === 'small' ? 24 : 48;
}
return indicatorSize ? indicatorSize : 24;
};
/**
* Activity indicator is used to present progress of some activity in the app.
* It can be used as a drop-in for the ActivityIndicator shipped with React Native.
*
*
*

*
*
* ## Usage
* ```js
* import * as React from 'react';
* import { ActivityIndicator, MD2Colors } from 'react-native-paper';
*
* const MyComponent = () => (
*
* );
*
* export default MyComponent;
* ```
*/
const ActivityIndicator = ({
animating = true,
color: indicatorColorProp,
hidesWhenStopped = true,
size: indicatorSize = 'small',
style: styleProp,
...rest
}: Props) => {
// const componentStyles = useComponentStyles('ActivityIndicator', [
// styleProp,
// { normalizedIndicatorColorProp: indicatorColorProp },
// ]);
const componentStyles = activityIndicatorStyles;
const { current: timer } = useRef(new Animated.Value(0));
const { current: fade } = useRef(
new Animated.Value(!animating && hidesWhenStopped ? 0 : 1),
);
const rotation = useRef(undefined);
const size = mapIndicatorSize(indicatorSize);
const { color, animationScale, viewStyle, animatedViewStyle } = useMemo(() => {
const { color: defaultIndicatorColor, animationScale: _animationScale } =
componentStyles.root;
return {
color: indicatorColorProp || defaultIndicatorColor,
animationScale: _animationScale,
viewStyle: [componentStyles.container, styleProp],
animatedViewStyle: [{ width: size, height: size, opacity: fade }],
};
}, [
componentStyles.container,
componentStyles.root,
fade,
indicatorColorProp,
size,
styleProp,
]);
const startRotation = useCallback(() => {
// Show indicator
Animated.timing(fade, {
duration: 200 * +animationScale,
toValue: 1,
isInteraction: false,
useNativeDriver: true,
}).start();
// Circular animation in loop
if (rotation.current) {
timer.setValue(0);
// $FlowFixMe
Animated.loop(rotation.current).start();
}
}, [animationScale, fade, timer]);
const stopRotation = () => {
if (rotation.current) {
rotation.current.stop();
}
};
useEffect(() => {
if (rotation.current === undefined) {
// Circular animation in loop
rotation.current = Animated.timing(timer, {
duration: DURATION,
easing: Easing.linear,
// Animated.loop does not work if useNativeDriver is true on web
useNativeDriver: Platform.OS !== 'web',
toValue: 1,
isInteraction: false,
});
}
if (animating) {
startRotation();
} else if (hidesWhenStopped) {
// Hide indicator first and then stop rotation
Animated.timing(fade, {
duration: 200 * +animationScale,
toValue: 0,
useNativeDriver: true,
isInteraction: false,
}).start(stopRotation);
} else {
stopRotation();
}
return () => {
if (animating) stopRotation();
};
}, [animating, fade, hidesWhenStopped, startRotation, animationScale, timer]);
// console.log({ layer: defaultStyles.layer });
return (
{[0, 1].map(index => {
// Thanks to https://github.com/n4kz/react-native-indicators for the great work
return (
);
})}
);
};
export const activityIndicatorStylesDefault = StyleSheet.create(theme => ({
root: {
animationScale: theme.animation.scale,
color: theme.colors.primary,
} as any,
container: {
justifyContent: 'center',
alignItems: 'center',
},
layer: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',
alignItems: 'center',
},
}));
registerComponentStyles('ActivityIndicator', activityIndicatorStylesDefault);
export const activityIndicatorStyles = getRegisteredMoleculesComponentStyles('ActivityIndicator');
export default memo(ActivityIndicator);