Skip to content
Open
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
119 changes: 50 additions & 69 deletions src/compose/ComposeMenu.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/* @flow strict-local */
import React, { PureComponent } from 'react';
import React, { useCallback } from 'react';
import { Platform, View } from 'react-native';
import type { DocumentPickerResponse } from 'react-native-document-picker';
import ImagePicker from 'react-native-image-picker';

import type { Dispatch, Narrow } from '../types';
import { connect } from '../react-redux';
import type { Narrow } from '../types';
import { useDispatch } from '../react-redux';
import { showErrorAlert } from '../utils/info';
import { BRAND_COLOR, createStyleSheet } from '../styles';
import {
Expand All @@ -20,7 +20,6 @@ import AnimatedComponent from '../animation/AnimatedComponent';
import { uploadFile } from '../actions';

type Props = $ReadOnly<{|
dispatch: Dispatch,
expanded: boolean,
destinationNarrow: Narrow,
insertVideoCallLink: (() => void) | null,
Expand Down Expand Up @@ -58,13 +57,18 @@ export const chooseUploadImageFilename = (uri: string, fileName: ?string): strin
return name;
};

class ComposeMenu extends PureComponent<Props> {
uploadFile = (uri: string, fileName: ?string) => {
const { dispatch, destinationNarrow } = this.props;
dispatch(uploadFile(destinationNarrow, uri, chooseUploadImageFilename(uri, fileName)));
};
export default function ComposeMenu(props: Props) {
const { destinationNarrow } = props;
const dispatch = useDispatch();

const uploadFileCallback = useCallback(
Copy link
Copy Markdown
Collaborator

@chrisbobbe chrisbobbe Jun 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This useCallback isn't doing much; uploadFileCallback's callers are functions that get recreated each time the ComposeMenu renders (they aren't using useCallback), and those functions are being passed to various children.

(uri: string, fileName: ?string) => {
dispatch(uploadFile(destinationNarrow, uri, chooseUploadImageFilename(uri, fileName)));
},
[dispatch, destinationNarrow],
);

handleImagePickerResponse = (
const handleImagePickerResponse = (
response: $ReadOnly<{
didCancel: boolean,
// Upstream docs are vague:
Expand All @@ -91,10 +95,10 @@ class ComposeMenu extends PureComponent<Props> {
return;
}

this.uploadFile(response.uri, response.fileName);
uploadFileCallback(response.uri, response.fileName);
};

handleImagePicker = () => {
const handleImagePicker = () => {
ImagePicker.launchImageLibrary(
{
quality: 1.0,
Expand All @@ -104,22 +108,22 @@ class ComposeMenu extends PureComponent<Props> {
path: 'images',
},
},
this.handleImagePickerResponse,
handleImagePickerResponse,
);
};

handleCameraCapture = () => {
const handleCameraCapture = () => {
const options = {
storageOptions: {
cameraRoll: true,
waitUntilSaved: true,
},
};

ImagePicker.launchCamera(options, this.handleImagePickerResponse);
ImagePicker.launchCamera(options, handleImagePickerResponse);
};

handleFilePicker = async () => {
const handleFilePicker = async () => {
// Defer import to here, to avoid an obnoxious import-time warning
// from this library when in the test environment.
const DocumentPicker = (await import('react-native-document-picker')).default;
Expand All @@ -136,10 +140,10 @@ class ComposeMenu extends PureComponent<Props> {
return;
}

this.uploadFile(response.uri, response.name);
uploadFileCallback(response.uri, response.name);
};

styles = createStyleSheet({
const styles = createStyleSheet({
composeMenu: {
flexDirection: 'row',
overflow: 'hidden',
Expand All @@ -155,55 +159,32 @@ class ComposeMenu extends PureComponent<Props> {
},
});

render() {
const { expanded, insertVideoCallLink, onExpandContract } = this.props;
const numIcons =
2 + (Platform.OS === 'android' ? 1 : 0) + (insertVideoCallLink !== null ? 1 : 0);

return (
<View style={this.styles.composeMenu}>
<AnimatedComponent
stylePropertyName="width"
fullValue={40 * numIcons}
useNativeDriver={false}
visible={expanded}
>
<View style={this.styles.composeMenu}>
{Platform.OS === 'android' && (
<IconFile
style={this.styles.composeMenuButton}
size={24}
onPress={this.handleFilePicker}
/>
)}
<IconImage
style={this.styles.composeMenuButton}
size={24}
onPress={this.handleImagePicker}
/>
<IconCamera
style={this.styles.composeMenuButton}
size={24}
onPress={this.handleCameraCapture}
/>
{insertVideoCallLink !== null ? (
<IconVideo
style={this.styles.composeMenuButton}
size={24}
onPress={insertVideoCallLink}
/>
) : null}
</View>
</AnimatedComponent>
{!expanded && (
<IconPlusCircle style={this.styles.expandButton} size={24} onPress={onExpandContract} />
)}
{expanded && (
<IconLeft style={this.styles.expandButton} size={24} onPress={onExpandContract} />
)}
</View>
);
}
const { expanded, insertVideoCallLink, onExpandContract } = props;
const numIcons = 2 + (Platform.OS === 'android' ? 1 : 0) + (insertVideoCallLink !== null ? 1 : 0);

return (
<View style={styles.composeMenu}>
<AnimatedComponent
stylePropertyName="width"
fullValue={40 * numIcons}
useNativeDriver={false}
visible={expanded}
>
<View style={styles.composeMenu}>
{Platform.OS === 'android' && (
<IconFile style={styles.composeMenuButton} size={24} onPress={handleFilePicker} />
)}
<IconImage style={styles.composeMenuButton} size={24} onPress={handleImagePicker} />
<IconCamera style={styles.composeMenuButton} size={24} onPress={handleCameraCapture} />
{insertVideoCallLink !== null ? (
<IconVideo style={styles.composeMenuButton} size={24} onPress={insertVideoCallLink} />
) : null}
</View>
</AnimatedComponent>
{!expanded && (
<IconPlusCircle style={styles.expandButton} size={24} onPress={onExpandContract} />
)}
{expanded && <IconLeft style={styles.expandButton} size={24} onPress={onExpandContract} />}
</View>
);
}

export default connect<{||}, _, _>()(ComposeMenu);