Skip to content

Commit

Permalink
Indicate media upload progress via spinner
Browse files Browse the repository at this point in the history
Closes #82.
  • Loading branch information
mhoran committed Mar 15, 2024
1 parent 2a64c1c commit a500124
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 8 deletions.
29 changes: 22 additions & 7 deletions __tests__/usecase/buffers/ui/UploadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react-native';
import * as FileSystem from 'expo-file-system';
import * as ImagePicker from 'expo-image-picker';
import { fireEvent, render, screen } from '@testing-library/react-native';
import UploadButton from '../../../../src/usecase/buffers/ui/UploadButton';

jest.mock('expo-file-system', () => {
Expand All @@ -13,6 +13,8 @@ jest.mock('expo-file-system', () => {
const mockedFileSystem = jest.mocked(FileSystem);

describe(UploadButton, () => {
let resolveUpload: (result: FileSystem.FileSystemUploadResult) => void;

beforeEach(() => {
jest.clearAllMocks();
jest
Expand All @@ -24,10 +26,7 @@ describe(UploadButton, () => {
});

mockedFileSystem.uploadAsync.mockImplementation(() => {
return Promise.resolve({
status: 200,
body: 'https://example.com/image.jpg'
} as FileSystem.FileSystemUploadResult);
return new Promise((resolve) => (resolveUpload = resolve));
});
});

Expand All @@ -44,7 +43,15 @@ describe(UploadButton, () => {

fireEvent(button, 'press');

await new Promise(process.nextTick);
await screen.findByLabelText('Image Uploading');

resolveUpload({
status: 200,
body: 'https://example.com/image.jpg'
} as FileSystem.FileSystemUploadResult);

await screen.findByLabelText('Upload Image');

expect(ImagePicker.launchImageLibraryAsync).toHaveBeenCalledWith({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsMultipleSelection: false,
Expand Down Expand Up @@ -112,7 +119,15 @@ describe(UploadButton, () => {

fireEvent(button, 'press');

await new Promise(process.nextTick);
await screen.findByLabelText('Image Uploading');

resolveUpload({
status: 200,
body: 'https://example.com/image.jpg'
} as FileSystem.FileSystemUploadResult);

await screen.findByLabelText('Upload Image');

expect(FileSystem.uploadAsync).toHaveBeenCalledWith(
'https://example.com',
'file:///tmp/image.jpg',
Expand Down
17 changes: 16 additions & 1 deletion src/usecase/buffers/ui/UploadButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { MaterialIcons } from '@expo/vector-icons';
import { Buffer } from 'buffer';
import * as FileSystem from 'expo-file-system';
import * as ImagePicker from 'expo-image-picker';
import { StyleProp, TouchableOpacity, ViewStyle } from 'react-native';
import { StyleProp, TouchableOpacity, View, ViewStyle } from 'react-native';
import UploadSpinner from './UploadSpinner';
import { useState } from 'react';

interface Props {
onUpload: (url: string) => void;
Expand All @@ -28,6 +30,8 @@ const UploadButton: React.FC<Props> = ({
...uploadOptions
}
}) => {
const [showSpinner, setShowSpinner] = useState(false);

const takePhoto = async () => {
const permission = await ImagePicker.requestCameraPermissionsAsync();

Expand Down Expand Up @@ -58,13 +62,16 @@ const UploadButton: React.FC<Props> = ({
if (pickerResult.canceled) {
return;
} else {
setShowSpinner(true);
const uploadUrl = await uploadImage(pickerResult.assets[0].uri);
const matches = uploadUrl.match(new RegExp(uploadOptionsRegexp));
if (!matches) return alert('Failed to extract URL from response');
onUpload(matches[1] || matches[0]);
}
} catch (e) {
alert('Upload failed');
} finally {
setShowSpinner(false);
}
};

Expand Down Expand Up @@ -96,6 +103,14 @@ const UploadButton: React.FC<Props> = ({
)
return;

if (showSpinner) {
return (
<View style={style} accessibilityLabel="Image Uploading">
<UploadSpinner />
</View>
);
}

return (
<TouchableOpacity
onPress={pickImage}
Expand Down
48 changes: 48 additions & 0 deletions src/usecase/buffers/ui/UploadSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { MaterialCommunityIcons } from '@expo/vector-icons';
import React, { useEffect } from 'react';
import Animated, {
Easing,
cancelAnimation,
useAnimatedStyle,
useSharedValue,
withRepeat,
withTiming
} from 'react-native-reanimated';

const AnimatedIcon = Animated.createAnimatedComponent(MaterialCommunityIcons);

const UploadSpinner: React.FC = () => {
const rotation = useSharedValue(0);

const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{
rotateZ: `${rotation.value}deg`
}
]
};
}, [rotation.value]);

useEffect(() => {
rotation.value = withRepeat(
withTiming(360, {
duration: 1000,
easing: Easing.linear
}),
200
);
return () => cancelAnimation(rotation);
}, [rotation]);

return (
<AnimatedIcon
name="loading"
size={27}
color="white"
style={animatedStyles}
/>
);
};

export default UploadSpinner;

0 comments on commit a500124

Please sign in to comment.