Files
jellyfin-audio-player/src/components/DownloadIcon.tsx

99 lines
3.4 KiB
TypeScript
Raw Normal View History

2022-01-02 19:16:12 +01:00
import React, { useEffect, useMemo, useRef } from 'react';
2022-01-02 02:28:52 +01:00
import { useTypedSelector } from 'store';
import CloudIcon from 'assets/cloud.svg';
import CloudExclamationMarkIcon from 'assets/cloud-exclamation-mark.svg';
import InternalDriveIcon from 'assets/internal-drive.svg';
import useDefaultStyles from './Colors';
import { EntityId } from '@reduxjs/toolkit';
2022-01-02 19:29:20 +01:00
import Svg, { Circle, CircleProps } from 'react-native-svg';
2022-01-02 19:16:12 +01:00
import { Animated, Easing } from 'react-native';
2022-01-02 02:28:52 +01:00
interface DownloadIconProps {
trackId: EntityId;
size?: number;
fill?: string;
}
function DownloadIcon({ trackId, size = 16, fill }: DownloadIconProps) {
2022-01-02 19:16:12 +01:00
// determine styles
2022-01-02 02:28:52 +01:00
const defaultStyles = useDefaultStyles();
const iconFill = fill || defaultStyles.textHalfOpacity.color;
2022-01-02 19:16:12 +01:00
// Get download icon from state
const entity = useTypedSelector((state) => state.downloads.entities[trackId]);
// Memoize calculations for radius and circumference of the circle
const radius = useMemo(() => size / 2, [size]);
const circumference = useMemo(() => radius * 2 * Math.PI, [radius]);
// Initialize refs for the circle and the animated value
const circleRef = useRef<Circle>(null);
const offsetAnimation = useRef(new Animated.Value(entity?.progress || 0)).current;
// Whenever the progress changes, trigger the animation
useEffect(() => {
Animated.timing(offsetAnimation, {
toValue: (circumference * (1 - (entity?.progress || 0))),
duration: 250,
useNativeDriver: false,
easing: Easing.ease,
}).start();
}, [entity?.progress, offsetAnimation, circumference]);
// On mount, subscribe to changes in the animation value and then
// apply them to the circle using native props
useEffect(() => {
const subscription = offsetAnimation.addListener((offset) => {
2022-01-02 19:29:20 +01:00
// @ts-expect-error undocumented functionality
const setNativeProps = circleRef.current?.setNativeProps as (props: CircleProps) => void | undefined;
setNativeProps?.({ strokeDashoffset: offset.value });
2022-01-02 19:16:12 +01:00
});
return () => offsetAnimation.removeListener(subscription);
}, [offsetAnimation]);
2022-01-02 02:28:52 +01:00
if (!entity) {
return (
<CloudIcon width={size} height={size} fill={iconFill} />
);
}
2022-01-02 19:16:12 +01:00
const { isComplete, isFailed } = entity;
2022-01-02 02:28:52 +01:00
if (isComplete) {
return (
<InternalDriveIcon width={size} height={size} fill={iconFill} />
);
}
if (isFailed) {
return (
<CloudExclamationMarkIcon width={size} height={size} fill={iconFill} />
);
}
if (!isComplete && !isFailed) {
return (
<Svg width={size} height={size} transform={[{ rotate: '-90deg' }]}>
<Circle
cx={radius}
cy={radius}
r={radius - 1}
stroke={iconFill}
2022-01-02 19:29:20 +01:00
// @ts-expect-error react-native-svg has outdated react-native typings
2022-01-02 19:16:12 +01:00
ref={circleRef}
2022-01-02 02:28:52 +01:00
strokeWidth={1.5}
strokeDasharray={[ circumference, circumference ]}
2022-01-02 19:16:12 +01:00
strokeDashoffset={circumference}
2022-01-02 02:28:52 +01:00
strokeLinecap='round'
fill='transparent'
/>
</Svg>
);
}
return null;
}
export default DownloadIcon;