Skip to content

Commit eeb0d4f

Browse files
committed
fix: too fast upload progress ui glitch
1 parent 8c2b9af commit eeb0d4f

4 files changed

Lines changed: 64 additions & 33 deletions

File tree

package/src/components/Attachment/Attachment.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from 'stream-chat';
1414

1515
import type { AudioAttachmentProps } from './Audio/AudioAttachment';
16+
1617
import { AttachmentFileUploadProgressIndicator } from '../../components/Attachment/AttachmentFileUploadProgressIndicator';
1718

1819
import { useTheme } from '../../contexts';

package/src/components/Attachment/CircularProgressIndicator.tsx

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
import React, { useEffect, useMemo, useRef } from 'react';
2-
import type { ColorValue } from 'react-native';
3-
import { Animated, Easing, StyleProp, ViewStyle } from 'react-native';
1+
import React, { useEffect, useMemo } from 'react';
2+
import type { ColorValue, StyleProp, ViewStyle } from 'react-native';
3+
import Animated, {
4+
cancelAnimation,
5+
Easing,
6+
useAnimatedProps,
7+
useAnimatedStyle,
8+
useSharedValue,
9+
withRepeat,
10+
withTiming,
11+
} from 'react-native-reanimated';
412
import Svg, { Circle } from 'react-native-svg';
513

14+
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
15+
const SPIN_DURATION_MS = 900;
16+
const PROGRESS_ANIMATION_DURATION_MS = 1200;
17+
618
export type CircularProgressIndicatorProps = {
719
/** Upload percent **0–100**. */
820
progress: number;
@@ -24,32 +36,8 @@ export const CircularProgressIndicator = ({
2436
style,
2537
testID,
2638
}: CircularProgressIndicatorProps) => {
27-
const spin = useRef(new Animated.Value(0)).current;
28-
29-
useEffect(() => {
30-
const loop = Animated.loop(
31-
Animated.timing(spin, {
32-
toValue: 1,
33-
duration: 900,
34-
easing: Easing.linear,
35-
useNativeDriver: true,
36-
}),
37-
);
38-
loop.start();
39-
return () => {
40-
loop.stop();
41-
spin.setValue(0);
42-
};
43-
}, [progress, spin]);
44-
45-
const rotate = useMemo(
46-
() =>
47-
spin.interpolate({
48-
inputRange: [0, 1],
49-
outputRange: ['0deg', '360deg'],
50-
}),
51-
[spin],
52-
);
39+
const animatedProgress = useSharedValue(0);
40+
const rotation = useSharedValue(0);
5341

5442
const { cx, cy, r, circumference } = useMemo(() => {
5543
const pad = strokeWidth / 2;
@@ -67,18 +55,58 @@ export const CircularProgressIndicator = ({
6755
? undefined
6856
: Math.min(100, Math.max(0, progress)) / 100;
6957

58+
useEffect(() => {
59+
if (fraction === undefined) {
60+
animatedProgress.value = 0;
61+
return;
62+
}
63+
64+
animatedProgress.value = withTiming(fraction, {
65+
duration: PROGRESS_ANIMATION_DURATION_MS,
66+
easing: Easing.out(Easing.cubic),
67+
});
68+
}, [animatedProgress, fraction]);
69+
70+
useEffect(() => {
71+
if (fraction !== undefined) {
72+
cancelAnimation(rotation);
73+
rotation.value = 0;
74+
return;
75+
}
76+
77+
rotation.value = withRepeat(
78+
withTiming(360, {
79+
duration: SPIN_DURATION_MS,
80+
easing: Easing.linear,
81+
}),
82+
-1,
83+
false,
84+
);
85+
86+
return () => {
87+
cancelAnimation(rotation);
88+
};
89+
}, [fraction, rotation]);
90+
91+
const animatedCircleProps = useAnimatedProps(() => ({
92+
strokeDashoffset: circumference * (1 - animatedProgress.value),
93+
}));
94+
95+
const animatedSpinStyle = useAnimatedStyle<ViewStyle>(() => ({
96+
transform: [{ rotate: `${rotation.value}deg` }],
97+
}));
98+
7099
if (fraction !== undefined) {
71-
const offset = circumference * (1 - fraction);
72100
return (
73101
<Svg height={size} style={style} testID={testID} viewBox={`0 0 ${size} ${size}`} width={size}>
74-
<Circle
102+
<AnimatedCircle
103+
animatedProps={animatedCircleProps}
75104
cx={cx}
76105
cy={cy}
77106
fill='none'
78107
r={r}
79108
stroke={color as string}
80109
strokeDasharray={`${circumference}`}
81-
strokeDashoffset={offset}
82110
strokeLinecap='round'
83111
strokeWidth={strokeWidth}
84112
transform={`rotate(-90 ${cx} ${cy})`}
@@ -92,7 +120,7 @@ export const CircularProgressIndicator = ({
92120

93121
return (
94122
<Animated.View
95-
style={[{ height: size, width: size }, style, { transform: [{ rotate }] }]}
123+
style={[{ height: size, width: size }, style, animatedSpinStyle]}
96124
testID={testID}
97125
>
98126
<Svg height={size} viewBox={`0 0 ${size} ${size}`} width={size}>

package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jest.mock('../../../native.ts', () => {
2424
isDocumentPickerAvailable: jest.fn(() => true),
2525
isImageMediaLibraryAvailable: jest.fn(() => true),
2626
isImagePickerAvailable: jest.fn(() => true),
27+
isNativeMultipartUploadAvailable: jest.fn(() => false),
2728
isSoundPackageAvailable: jest.fn(() => false),
2829
NativeHandlers: {
2930
Sound: {

package/src/components/MessageInput/__tests__/AudioAttachmentUploadPreview.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jest.mock('../../../native.ts', () => {
1919
isDocumentPickerAvailable: jest.fn(() => true),
2020
isImageMediaLibraryAvailable: jest.fn(() => true),
2121
isImagePickerAvailable: jest.fn(() => true),
22+
isNativeMultipartUploadAvailable: jest.fn(() => false),
2223
isSoundPackageAvailable: jest.fn(() => true),
2324
NativeHandlers: {
2425
Sound: {

0 commit comments

Comments
 (0)