Fixing post draft style to comply with design specs (#3818)

* Polishing post draft to comply with design specs

* changed maxHeight in landscape mode, fixed icon sizes

- Refactored code so that post draft icon sizes are taken from same constant value

- Set maxHeight value in landscape mode to be smaller (tests pending)

- Removed repeated styles for button wrappers (passing them down as props to child components)

- Increased size of image attachment remote icon, and increased tappable area

* Removing repeated logic for file upload

* Fixing failed snapshot tests / style checks

* Fixing file upload remove icon to have 64% opacity

* post draft UX/UI improvements

* Fix input box extra spacing

* input box line height and attachment border

* Animate to original state even if error is showing

* Fix permissions

* Improve attachment error animation

* Fix iOS post input height

* Update snapshots
This commit is contained in:
Andre Vasconcelos
2020-01-22 03:20:19 +09:00
committed by Amit Uttam
parent e05207412f
commit 0b81a9b4e0
16 changed files with 563 additions and 421 deletions

View File

@@ -5,8 +5,7 @@ exports[`SendButton should change theme backgroundColor to 0.3 opacity 1`] = `
style={ style={
Object { Object {
"justifyContent": "flex-end", "justifyContent": "flex-end",
"paddingHorizontal": 5, "paddingRight": 8,
"paddingVertical": 2,
} }
} }
> >
@@ -17,10 +16,9 @@ exports[`SendButton should change theme backgroundColor to 0.3 opacity 1`] = `
"alignItems": "center", "alignItems": "center",
"backgroundColor": "#166de0", "backgroundColor": "#166de0",
"borderRadius": 4, "borderRadius": 4,
"height": 28, "height": 32,
"justifyContent": "center", "justifyContent": "center",
"paddingLeft": 3, "width": 80,
"width": 72,
}, },
Object { Object {
"backgroundColor": "rgba(22,109,224,0.3)", "backgroundColor": "rgba(22,109,224,0.3)",
@@ -29,9 +27,9 @@ exports[`SendButton should change theme backgroundColor to 0.3 opacity 1`] = `
} }
> >
<PaperPlane <PaperPlane
color="#ffffff" color="rgba(255,255,255,0.5)"
height={13} height={16}
width={15} width={19}
/> />
</View> </View>
</View> </View>
@@ -43,8 +41,7 @@ exports[`SendButton should match snapshot 1`] = `
style={ style={
Object { Object {
"justifyContent": "flex-end", "justifyContent": "flex-end",
"paddingHorizontal": 5, "paddingRight": 8,
"paddingVertical": 2,
} }
} }
type="opacity" type="opacity"
@@ -55,17 +52,16 @@ exports[`SendButton should match snapshot 1`] = `
"alignItems": "center", "alignItems": "center",
"backgroundColor": "#166de0", "backgroundColor": "#166de0",
"borderRadius": 4, "borderRadius": 4,
"height": 28, "height": 32,
"justifyContent": "center", "justifyContent": "center",
"paddingLeft": 3, "width": 80,
"width": 72,
} }
} }
> >
<PaperPlane <PaperPlane
color="#ffffff" color="#ffffff"
height={13} height={16}
width={15} width={19}
/> />
</View> </View>
</TouchableWithFeedbackIOS> </TouchableWithFeedbackIOS>
@@ -77,8 +73,7 @@ exports[`SendButton should render theme backgroundColor 1`] = `
style={ style={
Object { Object {
"justifyContent": "flex-end", "justifyContent": "flex-end",
"paddingHorizontal": 5, "paddingRight": 8,
"paddingVertical": 2,
} }
} }
type="opacity" type="opacity"
@@ -89,17 +84,16 @@ exports[`SendButton should render theme backgroundColor 1`] = `
"alignItems": "center", "alignItems": "center",
"backgroundColor": "#166de0", "backgroundColor": "#166de0",
"borderRadius": 4, "borderRadius": 4,
"height": 28, "height": 32,
"justifyContent": "center", "justifyContent": "center",
"paddingLeft": 3, "width": 80,
"width": 72,
} }
} }
> >
<PaperPlane <PaperPlane
color="#ffffff" color="#ffffff"
height={13} height={16}
width={15} width={19}
/> />
</View> </View>
</TouchableWithFeedbackIOS> </TouchableWithFeedbackIOS>

View File

@@ -329,18 +329,19 @@ export default class AttachmentButton extends PureComponent {
hasStoragePermission = async () => { hasStoragePermission = async () => {
if (Platform.OS === 'android') { if (Platform.OS === 'android') {
const {formatMessage} = this.context.intl; const {formatMessage} = this.context.intl;
const storagePermission = Permissions.PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
let permissionRequest; let permissionRequest;
const hasPermissionToStorage = await Permissions.check('storage'); const hasPermissionToStorage = await Permissions.check(storagePermission);
switch (hasPermissionToStorage) { switch (hasPermissionToStorage) {
case Permissions.RESULTS.DENIED: case Permissions.RESULTS.DENIED:
permissionRequest = await Permissions.request('storage'); permissionRequest = await Permissions.request(storagePermission);
if (permissionRequest !== Permissions.RESULTS.GRANTED) { if (permissionRequest !== Permissions.RESULTS.GRANTED) {
return false; return false;
} }
break; break;
case Permissions.RESULTS.BLOCKED: { case Permissions.RESULTS.BLOCKED: {
const {title, text} = this.getPermissionDeniedMessage('storage'); const {title, text} = this.getPermissionDeniedMessage(storagePermission);
Alert.alert( Alert.alert(
title, title,

View File

@@ -3,7 +3,7 @@
import React, {PureComponent} from 'react'; import React, {PureComponent} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {Platform, StyleSheet, Text, View} from 'react-native'; import {Text, View} from 'react-native';
import RNFetchBlob from 'rn-fetch-blob'; import RNFetchBlob from 'rn-fetch-blob';
import {AnimatedCircularProgress} from 'react-native-circular-progress'; import {AnimatedCircularProgress} from 'react-native-circular-progress';
@@ -17,6 +17,7 @@ import mattermostBucket from 'app/mattermost_bucket';
import {buildFileUploadData, encodeHeaderURIStringToUTF8} from 'app/utils/file'; import {buildFileUploadData, encodeHeaderURIStringToUTF8} from 'app/utils/file';
import {emptyFunction} from 'app/utils/general'; import {emptyFunction} from 'app/utils/general';
import ImageCacheManager from 'app/utils/image_cache_manager'; import ImageCacheManager from 'app/utils/image_cache_manager';
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
export default class FileUploadItem extends PureComponent { export default class FileUploadItem extends PureComponent {
static propTypes = { static propTypes = {
@@ -163,6 +164,7 @@ export default class FileUploadItem extends PureComponent {
}; };
renderProgress = (fill) => { renderProgress = (fill) => {
const styles = getStyleSheet(this.props.theme);
const realFill = Number(fill.toFixed(0)); const realFill = Number(fill.toFixed(0));
return ( return (
@@ -184,23 +186,28 @@ export default class FileUploadItem extends PureComponent {
theme, theme,
} = this.props; } = this.props;
const {progress} = this.state; const {progress} = this.state;
const styles = getStyleSheet(theme);
let filePreviewComponent; let filePreviewComponent;
if (this.isImageType()) { if (this.isImageType()) {
filePreviewComponent = ( filePreviewComponent = (
<FileAttachmentImage <View style={styles.filePreview}>
file={file} <FileAttachmentImage
theme={theme} file={file}
/> theme={theme}
/>
</View>
); );
} else { } else {
filePreviewComponent = ( filePreviewComponent = (
<FileAttachmentIcon <View style={styles.filePreview}>
file={file} <FileAttachmentIcon
theme={theme} file={file}
wrapperHeight={100} theme={theme}
wrapperWidth={100} wrapperHeight={60}
/> wrapperWidth={60}
/>
</View>
); );
} }
@@ -209,7 +216,7 @@ export default class FileUploadItem extends PureComponent {
key={file.clientId} key={file.clientId}
style={styles.preview} style={styles.preview}
> >
<View style={styles.previewShadow}> <View style={styles.previewContainer}>
{filePreviewComponent} {filePreviewComponent}
{file.failed && {file.failed &&
<FileUploadRetry <FileUploadRetry
@@ -220,7 +227,7 @@ export default class FileUploadItem extends PureComponent {
{file.loading && !file.failed && {file.loading && !file.failed &&
<View style={styles.progressCircleContent}> <View style={styles.progressCircleContent}>
<AnimatedCircularProgress <AnimatedCircularProgress
size={100} size={64}
fill={progress} fill={progress}
width={4} width={4}
backgroundColor='rgba(255, 255, 255, 0.5)' backgroundColor='rgba(255, 255, 255, 0.5)'
@@ -245,29 +252,16 @@ export default class FileUploadItem extends PureComponent {
} }
} }
const styles = StyleSheet.create({ const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
preview: { preview: {
justifyContent: 'flex-end', paddingTop: 12,
height: 115, marginLeft: 12,
width: 115,
}, },
previewShadow: { previewContainer: {
height: 100, height: 64,
width: 100, width: 64,
elevation: 10, elevation: 10,
borderRadius: 5, borderRadius: 4,
...Platform.select({
ios: {
backgroundColor: '#fff',
shadowColor: '#000',
shadowOpacity: 0.5,
shadowRadius: 4,
shadowOffset: {
width: 0,
height: 0,
},
},
}),
}, },
progressCircle: { progressCircle: {
alignItems: 'center', alignItems: 'center',
@@ -278,10 +272,10 @@ const styles = StyleSheet.create({
progressCircleContent: { progressCircleContent: {
alignItems: 'center', alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.4)', backgroundColor: 'rgba(0, 0, 0, 0.4)',
height: 100, height: 64,
justifyContent: 'center', justifyContent: 'center',
position: 'absolute', position: 'absolute',
width: 100, width: 64,
}, },
progressCirclePercentage: { progressCirclePercentage: {
alignItems: 'center', alignItems: 'center',
@@ -300,4 +294,11 @@ const styles = StyleSheet.create({
color: 'white', color: 'white',
fontSize: 18, fontSize: 18,
}, },
}); filePreview: {
borderColor: changeOpacity(theme.centerChannelColor, 0.15),
borderRadius: 5,
borderWidth: 1,
width: 64,
height: 64,
},
}));

View File

@@ -3,6 +3,7 @@
import React from 'react'; import React from 'react';
import {shallow} from 'enzyme'; import {shallow} from 'enzyme';
import {Preferences} from 'mattermost-redux/constants';
import ImageCacheManager from 'app/utils/image_cache_manager'; import ImageCacheManager from 'app/utils/image_cache_manager';
import FileUploadItem from './file_upload_item'; import FileUploadItem from './file_upload_item';
@@ -18,7 +19,7 @@ describe('FileUploadItem', () => {
file: { file: {
loading: false, loading: false,
}, },
theme: {}, theme: Preferences.THEMES.default,
}; };
describe('downloadAndUploadFile', () => { describe('downloadAndUploadFile', () => {

View File

@@ -4,19 +4,26 @@
import React, {PureComponent} from 'react'; import React, {PureComponent} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
Platform, InteractionManager,
ScrollView, ScrollView,
Text, Text,
View, View,
} from 'react-native'; } from 'react-native';
import {makeStyleSheetFromTheme} from 'app/utils/theme'; import * as Animatable from 'react-native-animatable';
import EventEmitter from 'mattermost-redux/utils/event_emitter'; import EventEmitter from 'mattermost-redux/utils/event_emitter';
import FormattedText from 'app/components/formatted_text'; import FormattedText from 'app/components/formatted_text';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import FileUploadItem from './file_upload_item'; import FileUploadItem from './file_upload_item';
const initial = {opacity: 0, scale: 0};
const final = {opacity: 1, scale: 1};
const showFiles = {opacity: 1, height: 81};
const hideFiles = {opacity: 0, height: 0};
const hideError = {height: 0};
export default class FileUploadPreview extends PureComponent { export default class FileUploadPreview extends PureComponent {
static propTypes = { static propTypes = {
channelId: PropTypes.string.isRequired, channelId: PropTypes.string.isRequired,
@@ -36,9 +43,17 @@ export default class FileUploadPreview extends PureComponent {
showFileMaxWarning: false, showFileMaxWarning: false,
}; };
errorRef = React.createRef();
errorContainerRef = React.createRef();
containerRef = React.createRef();
componentDidMount() { componentDidMount() {
EventEmitter.on('fileMaxWarning', this.handleFileMaxWarning); EventEmitter.on('fileMaxWarning', this.handleFileMaxWarning);
EventEmitter.on('fileSizeWarning', this.handleFileSizeWarning); EventEmitter.on('fileSizeWarning', this.handleFileSizeWarning);
if (this.props.files.length) {
InteractionManager.runAfterInteractions(this.showOrHideContainer);
}
} }
componentWillUnmount() { componentWillUnmount() {
@@ -46,6 +61,12 @@ export default class FileUploadPreview extends PureComponent {
EventEmitter.off('fileSizeWarning', this.handleFileSizeWarning); EventEmitter.off('fileSizeWarning', this.handleFileSizeWarning);
} }
componentDidUpdate(prevProps) {
if (this.containerRef.current && this.props.files.length !== prevProps.files.length) {
InteractionManager.runAfterInteractions(this.showOrHideContainer);
}
}
buildFilePreviews = () => { buildFilePreviews = () => {
return this.props.files.map((file) => { return this.props.files.map((file) => {
return ( return (
@@ -60,35 +81,88 @@ export default class FileUploadPreview extends PureComponent {
}); });
}; };
clearErrorsFromState = (delay) => {
setTimeout(() => {
this.setState({
showFileMaxWarning: false,
fileSizeWarning: null,
});
}, delay || 0);
}
handleFileMaxWarning = () => { handleFileMaxWarning = () => {
this.setState({showFileMaxWarning: true}); this.setState({showFileMaxWarning: true});
setTimeout(() => { if (this.errorRef.current) {
this.setState({showFileMaxWarning: false}); this.makeErrorVisible(true, 20, null, () => {
}, 3000); this.errorRef.current.transition(initial, final, 350, 'ease-in');
});
setTimeout(() => {
this.makeErrorVisible(false, 20, 350, () => {
this.errorRef.current.transition(final, initial, 350, 'ease-out');
this.clearErrorsFromState(400);
});
}, 5000);
}
}; };
handleFileSizeWarning = (message) => { handleFileSizeWarning = (message) => {
this.setState({fileSizeWarning: message}); if (this.errorRef.current) {
if (message) {
this.setState({fileSizeWarning: message});
this.makeErrorVisible(true, 42, null, () => {
this.errorRef.current.transition(initial, final, 350, 'ease-in');
});
} else {
this.makeErrorVisible(false, 42, 350, () => {
this.errorRef.current.transition(final, initial, 350, 'ease-out');
this.clearErrorsFromState(400);
});
}
}
}; };
render() { makeErrorVisible = (visible, height, delay, callback) => {
if (this.errorContainerRef.current) {
if (visible) {
this.errorContainerRef.current.transition(hideError, {height}, 100);
setTimeout(callback, delay || 150);
} else {
callback();
setTimeout(() => {
this.errorContainerRef.current.transition({height}, hideError, 300);
}, delay || 150);
}
}
}
showOrHideContainer = () => {
const { const {
channelIsLoading, channelIsLoading,
filesUploadingForCurrentChannel, filesUploadingForCurrentChannel,
files, files,
} = this.props; } = this.props;
if ((channelIsLoading || (!files.length && !filesUploadingForCurrentChannel))) {
this.containerRef.current.transition(showFiles, hideFiles, 150, 'ease-out');
this.shown = false;
} else if (files.length && !this.shown) {
this.containerRef.current.transition(hideFiles, showFiles, 350, 'ease-in');
this.shown = true;
}
}
render() {
const {fileSizeWarning, showFileMaxWarning} = this.state; const {fileSizeWarning, showFileMaxWarning} = this.state;
const style = getStyleSheet(this.props.theme); const style = getStyleSheet(this.props.theme);
if (
!fileSizeWarning && !showFileMaxWarning &&
(channelIsLoading || (!files.length && !filesUploadingForCurrentChannel))
) {
return null;
}
return ( return (
<View style={style.previewContainer}> <View style={style.previewContainer}>
<View style={style.fileContainer}> <Animatable.View
style={style.fileContainer}
ref={this.containerRef}
isInteraction={true}
duration={300}
>
<ScrollView <ScrollView
horizontal={true} horizontal={true}
style={style.scrollView} style={style.scrollView}
@@ -97,21 +171,32 @@ export default class FileUploadPreview extends PureComponent {
> >
{this.buildFilePreviews()} {this.buildFilePreviews()}
</ScrollView> </ScrollView>
</View> </Animatable.View>
<View style={style.errorContainer}> <Animatable.View
{showFileMaxWarning && ( ref={this.errorContainerRef}
<FormattedText style={style.errorContainer}
style={style.warning} isInteraction={true}
id='mobile.file_upload.max_warning' >
defaultMessage='Uploads limited to 5 files maximum.' <Animatable.View
/> ref={this.errorRef}
)} isInteraction={true}
{Boolean(fileSizeWarning) && style={style.errorTextContainer}
<Text style={style.warning}> useNativeDriver={true}
{fileSizeWarning} >
</Text> {showFileMaxWarning && (
} <FormattedText
</View> style={style.warning}
id='mobile.file_upload.max_warning'
defaultMessage='Uploads limited to 5 files maximum.'
/>
)}
{Boolean(fileSizeWarning) &&
<Text style={style.warning}>
{fileSizeWarning}
</Text>
}
</Animatable.View>
</Animatable.View>
</View> </View>
); );
} }
@@ -119,32 +204,36 @@ export default class FileUploadPreview extends PureComponent {
const getStyleSheet = makeStyleSheetFromTheme((theme) => { const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return { return {
fileContainer: {
display: 'flex',
flexDirection: 'row',
},
errorContainer: {
height: 18,
},
previewContainer: { previewContainer: {
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
}, },
fileContainer: {
display: 'flex',
flexDirection: 'row',
height: 0,
alignItems: 'center',
},
errorContainer: {
height: 0,
},
errorTextContainer: {
marginTop: 5,
marginHorizontal: 12,
opacity: 0,
flex: 1,
},
scrollView: { scrollView: {
flex: 1, flex: 1,
marginBottom: 10,
}, },
scrollViewContent: { scrollViewContent: {
alignItems: 'flex-end', alignItems: 'flex-end',
marginLeft: 14, paddingRight: 12,
}, },
warning: { warning: {
color: theme.errorTextColor, color: theme.errorTextColor,
marginLeft: 14, flex: 1,
marginBottom: Platform.select({ flexWrap: 'wrap',
android: 14,
ios: 0,
}),
}, },
}; };
}); });

View File

@@ -3,9 +3,9 @@
import React, {PureComponent} from 'react'; import React, {PureComponent} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {Platform} from 'react-native'; import {View} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {makeStyleSheetFromTheme} from 'app/utils/theme'; import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
import TouchableWithFeedback from 'app/components/touchable_with_feedback'; import TouchableWithFeedback from 'app/components/touchable_with_feedback';
@@ -25,19 +25,21 @@ export default class FileUploadRemove extends PureComponent {
}; };
render() { render() {
const style = getStyleSheet(this.props.theme); const {theme} = this.props;
const style = getStyleSheet(theme);
return ( return (
<TouchableWithFeedback <TouchableWithFeedback
style={style.removeButtonWrapper} style={style.tappableContainer}
onPress={this.handleOnPress} onPress={this.handleOnPress}
type={'opacity'} type={'opacity'}
> >
<Icon <View style={style.removeButton}>
name='close-circle' <Icon
color={this.props.theme.centerChannelColor} name='close-circle'
size={20} color={changeOpacity(theme.centerChannelColor, 0.64)}
style={style.removeButtonIcon} size={18}
/> />
</View>
</TouchableWithFeedback> </TouchableWithFeedback>
); );
} }
@@ -45,25 +47,20 @@ export default class FileUploadRemove extends PureComponent {
const getStyleSheet = makeStyleSheetFromTheme((theme) => { const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return { return {
removeButtonIcon: Platform.select({ tappableContainer: {
ios: {
marginTop: 2,
},
}),
removeButtonWrapper: {
alignItems: 'center',
justifyContent: 'center',
position: 'absolute', position: 'absolute',
overflow: 'hidden',
elevation: 11, elevation: 11,
top: 7, top: -2,
right: 7, right: -16,
width: 24, width: 32,
height: 24, height: 32,
borderRadius: 12, },
removeButton: {
borderRadius: 20,
alignSelf: 'center',
paddingTop: 6,
paddingHorizontal: 1,
backgroundColor: theme.centerChannelBg, backgroundColor: theme.centerChannelBg,
borderWidth: 2,
borderColor: theme.centerChannelBg,
}, },
}; };
}); });

View File

@@ -18,9 +18,7 @@ export class PasteableTextInput extends React.PureComponent {
forwardRef: PropTypes.any, forwardRef: PropTypes.any,
} }
state = { inputHeight = new Animated.Value(ViewTypes.INPUT_INITIAL_HEIGHT);
inputHeight: new Animated.Value(33),
};
componentDidMount() { componentDidMount() {
this.subscription = OnPasteEventEmitter.addListener('onPaste', this.onPaste); this.subscription = OnPasteEventEmitter.addListener('onPaste', this.onPaste);
@@ -40,22 +38,33 @@ export class PasteableTextInput extends React.PureComponent {
animateHeight = (event) => { animateHeight = (event) => {
if (Platform.OS === 'ios') { if (Platform.OS === 'ios') {
const {height} = event.nativeEvent.contentSize; const {height} = event.nativeEvent.contentSize;
const {style} = this.props; const {style, value} = this.props;
const {inputHeight} = this.state;
const newHeight = Math.min(style.maxHeight, height + ViewTypes.INPUT_VERTICAL_PADDING); const newHeight = Math.min(style.maxHeight, height + ViewTypes.INPUT_VERTICAL_PADDING);
const transitionSpeed = height === ViewTypes.INPUT_LINE_HEIGHT ? 500 : 1;
Animated.timing(inputHeight, { if (value) {
toValue: newHeight, this.inputHeight.setValue(newHeight);
duration: transitionSpeed, } else {
easing: Easing.inOut(Easing.sin), requestAnimationFrame(() => {
}).start(); Animated.timing(this.inputHeight, {
toValue: ViewTypes.INPUT_INITIAL_HEIGHT,
duration: 350,
delay: 100,
easing: Easing.inOut(Easing.sin),
}).start();
});
}
} }
} }
wrapperLayout = (children) => { wrapperLayout = (children) => {
const {inputHeight} = this.state; return (
return <Animated.View style={{flex: 1, height: inputHeight}}>{children}</Animated.View>; <Animated.View
ref={this.containerRef}
style={{height: this.inputHeight}}
>
{children}
</Animated.View>
);
} }
render() { render() {

View File

@@ -14,7 +14,7 @@ exports[`PostTextBox should match, full snapshot 1`] = `
"borderTopWidth": 1, "borderTopWidth": 1,
"flexDirection": "row", "flexDirection": "row",
"justifyContent": "center", "justifyContent": "center",
"paddingVertical": 4, "paddingBottom": 8,
}, },
null, null,
] ]
@@ -36,11 +36,8 @@ exports[`PostTextBox should match, full snapshot 1`] = `
style={ style={
Array [ Array [
Object { Object {
"backgroundColor": "#ffffff",
"flex": 1, "flex": 1,
"flexDirection": "column", "flexDirection": "column",
"marginLeft": 10,
"marginRight": 10,
}, },
] ]
} }
@@ -61,100 +58,249 @@ exports[`PostTextBox should match, full snapshot 1`] = `
style={ style={
Object { Object {
"color": "#3d3c40", "color": "#3d3c40",
"fontSize": 14, "fontSize": 16,
"lineHeight": 20,
"maxHeight": 150, "maxHeight": 150,
"paddingBottom": 8, "minHeight": 38,
"paddingLeft": 12, "paddingBottom": 6,
"paddingRight": 12, "paddingHorizontal": 12,
"paddingTop": 8, "paddingTop": 12,
} }
} }
underlineColorAndroid="transparent" underlineColorAndroid="transparent"
value="" value=""
/> />
<Connect(FileUploadPreview) <React.Fragment>
files={Array []} <Connect(FileUploadPreview)
rootId="" files={Array []}
/> rootId=""
<View />
style={
Object {
"alignItems": "center",
"display": "flex",
"flexDirection": "row",
"justifyContent": "space-between",
}
}
>
<View <View
style={ style={
Object { Object {
"alignItems": "center",
"display": "flex", "display": "flex",
"flexDirection": "row", "flexDirection": "row",
"justifyContent": "space-between",
} }
} }
> >
<TouchableOpacity <View
activeOpacity={0.2}
disabled={false}
onPress={[Function]}
style={ style={
Object { Object {
"paddingLeft": 10, "display": "flex",
"paddingRight": 10, "flexDirection": "row",
"height": 44,
} }
} }
> >
<Icon <TouchableOpacity
allowFontScaling={false} activeOpacity={0.2}
color="#3d3c40" disabled={false}
name="at" onPress={[Function]}
size={20} style={
/>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.2}
disabled={false}
onPress={[Function]}
style={
Object {
"paddingLeft": 10,
"paddingRight": 10,
}
}
>
<Image
source={
Object { Object {
"testUri": "../../../dist/assets/images/icons/slash-forward-box.png", "alignItems": "center",
"justifyContent": "center",
"padding": 10,
} }
} }
>
<Icon
allowFontScaling={false}
color="rgba(61,60,64,0.64)"
name="at"
size={24}
/>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.2}
disabled={false}
onPress={[Function]}
style={ style={
Array [ Object {
"alignItems": "center",
"justifyContent": "center",
"padding": 10,
}
}
>
<Image
source={
Object { Object {
"height": 20, "testUri": "../../../dist/assets/images/icons/slash-forward-box.png",
"opacity": 1, }
"tintColor": "#3d3c40", }
"width": 20, style={
}, Array [
] Object {
"height": 24,
"opacity": 1,
"tintColor": "rgba(61,60,64,0.64)",
"width": 24,
},
]
}
/>
</TouchableOpacity>
<FileUploadButton
blurTextBox={[Function]}
browseFileTypes="public.item"
buttonContainerStyle={
Object {
"alignItems": "center",
"justifyContent": "center",
"padding": 10,
}
} }
canBrowseFiles={true}
canBrowsePhotoLibrary={true}
canBrowseVideoLibrary={true}
canTakePhoto={true}
canTakeVideo={true}
extraOptions={null}
fileCount={0}
maxFileCount={5}
maxFileSize={1024}
onShowFileMaxWarning={[Function]}
onShowFileSizeWarning={[Function]}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
uploadFiles={[Function]}
validMimeTypes={Array []}
/> />
</TouchableOpacity> <ImageUploadButton
<FileUploadButton blurTextBox={[Function]}
blurTextBox={[Function]} browseFileTypes="public.item"
browseFileTypes="public.item" buttonContainerStyle={
canBrowseFiles={true} Object {
canBrowsePhotoLibrary={true} "alignItems": "center",
canBrowseVideoLibrary={true} "justifyContent": "center",
canTakePhoto={true} "padding": 10,
canTakeVideo={true} }
extraOptions={null} }
fileCount={0} canBrowseFiles={true}
maxFileCount={5} canBrowsePhotoLibrary={true}
maxFileSize={1024} canBrowseVideoLibrary={true}
onShowFileMaxWarning={[Function]} canTakePhoto={true}
onShowFileSizeWarning={[Function]} canTakeVideo={true}
extraOptions={null}
fileCount={0}
maxFileCount={5}
maxFileSize={1024}
onShowFileMaxWarning={[Function]}
onShowFileSizeWarning={[Function]}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
uploadFiles={[Function]}
validMimeTypes={Array []}
/>
<AttachmentButton
blurTextBox={[Function]}
buttonContainerStyle={
Object {
"alignItems": "center",
"justifyContent": "center",
"padding": 10,
}
}
canTakePhoto={true}
canTakeVideo={true}
fileCount={0}
maxFileCount={5}
maxFileSize={1024}
onShowFileMaxWarning={[Function]}
onShowFileSizeWarning={[Function]}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
uploadFiles={[Function]}
validMimeTypes={Array []}
/>
</View>
<SendButton
disabled={true}
handleSendMessage={[Function]}
theme={ theme={
Object { Object {
"awayIndicator": "#ffbc42", "awayIndicator": "#ffbc42",
@@ -184,131 +330,9 @@ exports[`PostTextBox should match, full snapshot 1`] = `
"type": "Mattermost", "type": "Mattermost",
} }
} }
uploadFiles={[Function]}
validMimeTypes={Array []}
/>
<ImageUploadButton
blurTextBox={[Function]}
browseFileTypes="public.item"
canBrowseFiles={true}
canBrowsePhotoLibrary={true}
canBrowseVideoLibrary={true}
canTakePhoto={true}
canTakeVideo={true}
extraOptions={null}
fileCount={0}
maxFileCount={5}
maxFileSize={1024}
onShowFileMaxWarning={[Function]}
onShowFileSizeWarning={[Function]}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
uploadFiles={[Function]}
validMimeTypes={Array []}
/>
<AttachmentButton
blurTextBox={[Function]}
canTakePhoto={true}
canTakeVideo={true}
fileCount={0}
maxFileCount={5}
maxFileSize={1024}
onShowFileMaxWarning={[Function]}
onShowFileSizeWarning={[Function]}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
uploadFiles={[Function]}
validMimeTypes={Array []}
/> />
</View> </View>
<SendButton </React.Fragment>
disabled={true}
handleSendMessage={[Function]}
theme={
Object {
"awayIndicator": "#ffbc42",
"buttonBg": "#166de0",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3d3c40",
"codeTheme": "github",
"dndIndicator": "#f74343",
"errorTextColor": "#fd5960",
"linkColor": "#2389d7",
"mentionBg": "#ffffff",
"mentionBj": "#ffffff",
"mentionColor": "#145dbf",
"mentionHighlightBg": "#ffe577",
"mentionHighlightLink": "#166de0",
"newMessageSeparator": "#ff8800",
"onlineIndicator": "#06d6a0",
"sidebarBg": "#145dbf",
"sidebarHeaderBg": "#1153ab",
"sidebarHeaderTextColor": "#ffffff",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#579eff",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#4578bf",
"sidebarUnreadText": "#ffffff",
"type": "Mattermost",
}
}
/>
</View>
</ScrollView> </ScrollView>
</View> </View>
</React.Fragment> </React.Fragment>

View File

@@ -106,7 +106,7 @@ export default class AttachmentButton extends PureComponent {
if (Platform.OS === 'ios') { if (Platform.OS === 'ios') {
const {formatMessage} = this.context.intl; const {formatMessage} = this.context.intl;
let permissionRequest; let permissionRequest;
const targetSource = 'camera'; const targetSource = Permissions.PERMISSIONS.IOS.CAMERA;
const hasPermissionToStorage = await Permissions.check(targetSource); const hasPermissionToStorage = await Permissions.check(targetSource);
switch (hasPermissionToStorage) { switch (hasPermissionToStorage) {

View File

@@ -96,11 +96,12 @@ export default class FileUploadButton extends PureComponent {
if (Platform.OS === 'android') { if (Platform.OS === 'android') {
const {formatMessage} = this.context.intl; const {formatMessage} = this.context.intl;
let permissionRequest; let permissionRequest;
const hasPermissionToStorage = await Permissions.check('storage'); const storagePermission = Permissions.PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
const hasPermissionToStorage = await Permissions.check(storagePermission);
switch (hasPermissionToStorage) { switch (hasPermissionToStorage) {
case Permissions.RESULTS.UNAVAILABLE: case Permissions.RESULTS.UNAVAILABLE:
permissionRequest = await Permissions.request('storage'); permissionRequest = await Permissions.request(storagePermission);
if (permissionRequest !== Permissions.RESULTS.AUTHORIZED) { if (permissionRequest !== Permissions.RESULTS.AUTHORIZED) {
return false; return false;
} }

View File

@@ -108,7 +108,7 @@ export default class ImageUploadButton extends PureComponent {
if (Platform.OS === 'ios') { if (Platform.OS === 'ios') {
const {formatMessage} = this.context.intl; const {formatMessage} = this.context.intl;
let permissionRequest; let permissionRequest;
const targetSource = 'photo'; const targetSource = Permissions.PERMISSIONS.IOS.PHOTO_LIBRARY;
const hasPermissionToStorage = await Permissions.check(targetSource); const hasPermissionToStorage = await Permissions.check(targetSource);
switch (hasPermissionToStorage) { switch (hasPermissionToStorage) {

View File

@@ -19,6 +19,7 @@ import {
View, View,
} from 'react-native'; } from 'react-native';
import {intlShape} from 'react-intl'; import {intlShape} from 'react-intl';
import RNFetchBlob from 'rn-fetch-blob';
import Button from 'react-native-button'; import Button from 'react-native-button';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import slashForwardBoxIcon from 'assets/images/icons/slash-forward-box.png'; import slashForwardBoxIcon from 'assets/images/icons/slash-forward-box.png';
@@ -35,7 +36,7 @@ import FormattedText from 'app/components/formatted_text';
import PasteableTextInput from 'app/components/pasteable_text_input'; import PasteableTextInput from 'app/components/pasteable_text_input';
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing'; import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
import SendButton from 'app/components/send_button'; import SendButton from 'app/components/send_button';
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT, IS_REACTION_REGEX, MAX_FILE_COUNT} from 'app/constants/post_textbox'; import {INSERT_TO_COMMENT, INSERT_TO_DRAFT, IS_REACTION_REGEX, MAX_FILE_COUNT, ICON_SIZE} from 'app/constants/post_textbox';
import {NOTIFY_ALL_MEMBERS} from 'app/constants/view'; import {NOTIFY_ALL_MEMBERS} from 'app/constants/view';
import FileUploadPreview from 'app/components/file_upload_preview'; import FileUploadPreview from 'app/components/file_upload_preview';
@@ -239,13 +240,23 @@ export default class PostTextBoxBase extends PureComponent {
} }
}; };
startAtMention = () => {
this.handleTextChange(`${this.state.value}@`, true);
this.focus();
};
startSlashCommand = () => {
this.handleTextChange('/', true);
this.focus();
};
getTextInputButton = (actionType) => { getTextInputButton = (actionType) => {
const {channelIsReadOnly, theme} = this.props; const {channelIsReadOnly, theme} = this.props;
const style = getStyleSheet(theme); const style = getStyleSheet(theme);
let button = null; let button = null;
const buttonStyle = []; const buttonStyle = [];
let iconColor = theme.centerChannelColor; let iconColor = changeOpacity(theme.centerChannelColor, 0.64);
let isDisabled = false; let isDisabled = false;
if (!channelIsReadOnly) { if (!channelIsReadOnly) {
@@ -253,21 +264,18 @@ export default class PostTextBoxBase extends PureComponent {
case 'at': case 'at':
isDisabled = this.state.value[this.state.value.length - 1] === '@'; isDisabled = this.state.value[this.state.value.length - 1] === '@';
if (isDisabled) { if (isDisabled) {
iconColor = changeOpacity(theme.centerChannelColor, 0.6); iconColor = changeOpacity(theme.centerChannelColor, 0.16);
} }
button = ( button = (
<TouchableOpacity <TouchableOpacity
disabled={isDisabled} disabled={isDisabled}
onPress={() => { onPress={this.startAtMention}
this.handleTextChange(`${this.state.value}@`, true);
this.focus();
}}
style={style.iconWrapper} style={style.iconWrapper}
> >
<MaterialCommunityIcons <MaterialCommunityIcons
color={iconColor} color={iconColor}
name='at' name='at'
size={20} size={ICON_SIZE}
/> />
</TouchableOpacity> </TouchableOpacity>
); );
@@ -282,10 +290,7 @@ export default class PostTextBoxBase extends PureComponent {
button = ( button = (
<TouchableOpacity <TouchableOpacity
disabled={isDisabled} disabled={isDisabled}
onPress={() => { onPress={this.startSlashCommand}
this.handleTextChange('/', true);
this.focus();
}}
style={style.iconWrapper} style={style.iconWrapper}
> >
<Image <Image
@@ -303,6 +308,7 @@ export default class PostTextBoxBase extends PureComponent {
getMediaButton = (actionType) => { getMediaButton = (actionType) => {
const {canUploadFiles, channelIsReadOnly, files, maxFileSize, theme} = this.props; const {canUploadFiles, channelIsReadOnly, files, maxFileSize, theme} = this.props;
const style = getStyleSheet(theme);
let button = null; let button = null;
const props = { const props = {
blurTextBox: this.blur, blurTextBox: this.blur,
@@ -313,6 +319,7 @@ export default class PostTextBoxBase extends PureComponent {
uploadFiles: this.handleUploadFiles, uploadFiles: this.handleUploadFiles,
maxFileSize, maxFileSize,
theme, theme,
buttonContainerStyle: style.iconWrapper,
}; };
if (canUploadFiles && !channelIsReadOnly) { if (canUploadFiles && !channelIsReadOnly) {
@@ -504,8 +511,20 @@ export default class PostTextBoxBase extends PureComponent {
} }
}; };
handleUploadFiles = (files) => { handleUploadFiles = async (files) => {
this.props.actions.initUploadFiles(files, this.props.rootId); const file = files[0];
if (!file.fileSize | !file.fileName) {
const path = (file.path || file.uri).replace('file://', '');
const fileInfo = await RNFetchBlob.fs.stat(path);
file.fileSize = fileInfo.size;
file.fileName = fileInfo.filename;
}
if (file.fileSize > this.props.maxFileSize) {
this.onShowFileSizeWarning(file.fileName);
} else {
this.props.actions.initUploadFiles(files, this.props.rootId);
}
}; };
isFileLoading = () => { isFileLoading = () => {
@@ -722,7 +741,7 @@ export default class PostTextBoxBase extends PureComponent {
EventEmitter.emit('fileSizeWarning', fileSizeWarning); EventEmitter.emit('fileSizeWarning', fileSizeWarning);
setTimeout(() => { setTimeout(() => {
EventEmitter.emit('fileSizeWarning', null); EventEmitter.emit('fileSizeWarning', null);
}, 3000); }, 5000);
}; };
onCloseChannelPress = () => { onCloseChannelPress = () => {
@@ -835,6 +854,12 @@ export default class PostTextBoxBase extends PureComponent {
const textValue = channelIsLoading ? '' : value; const textValue = channelIsLoading ? '' : value;
const placeholder = this.getPlaceHolder(); const placeholder = this.getPlaceHolder();
let maxHeight = 150;
if (isLandscape) {
maxHeight = 88;
}
return ( return (
<View <View
style={[style.inputWrapper, padding(isLandscape)]} style={[style.inputWrapper, padding(isLandscape)]}
@@ -861,7 +886,7 @@ export default class PostTextBoxBase extends PureComponent {
multiline={true} multiline={true}
blurOnSubmit={false} blurOnSubmit={false}
underlineColorAndroid='transparent' underlineColorAndroid='transparent'
style={style.input} style={{...style.input, maxHeight}}
keyboardType={this.state.keyboardType} keyboardType={this.state.keyboardType}
onEndEditing={this.handleEndEditing} onEndEditing={this.handleEndEditing}
disableFullscreenUI={true} disableFullscreenUI={true}
@@ -869,32 +894,35 @@ export default class PostTextBoxBase extends PureComponent {
onPaste={this.handlePasteFiles} onPaste={this.handlePasteFiles}
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)} keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
/> />
{!channelIsReadOnly &&
<FileUploadPreview <React.Fragment>
files={files} <FileUploadPreview
rootId={rootId} files={files}
/> rootId={rootId}
<View style={style.buttonsContainer}>
<View style={style.quickActionsContainer}>
{this.getTextInputButton('at')}
{this.getTextInputButton('slash')}
{this.getMediaButton('file')}
{this.getMediaButton('image')}
{this.getMediaButton('camera')}
</View>
<SendButton
disabled={!this.isSendButtonEnabled()}
handleSendMessage={this.handleSendMessage}
theme={theme}
/> />
</View>
<View style={style.buttonsContainer}>
<View style={style.quickActionsContainer}>
{this.getTextInputButton('at')}
{this.getTextInputButton('slash')}
{this.getMediaButton('file')}
{this.getMediaButton('image')}
{this.getMediaButton('camera')}
</View>
<SendButton
disabled={!this.isSendButtonEnabled()}
handleSendMessage={this.handleSendMessage}
theme={theme}
/>
</View>
</React.Fragment>
}
</ScrollView> </ScrollView>
</View> </View>
); );
@@ -910,37 +938,36 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
alignItems: 'center', alignItems: 'center',
}, },
slashIcon: { slashIcon: {
width: 20, width: ICON_SIZE,
height: 20, height: ICON_SIZE,
opacity: 1, opacity: 1,
tintColor: theme.centerChannelColor, tintColor: changeOpacity(theme.centerChannelColor, 0.64),
}, },
iconDisabled: { iconDisabled: {
tintColor: changeOpacity(theme.centerChannelColor, 0.6), tintColor: changeOpacity(theme.centerChannelColor, 0.16),
}, },
iconWrapper: { iconWrapper: {
paddingLeft: 10, alignItems: 'center',
paddingRight: 10, justifyContent: 'center',
padding: 10,
}, },
quickActionsContainer: { quickActionsContainer: {
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
height: 44,
}, },
input: { input: {
color: theme.centerChannelColor, color: theme.centerChannelColor,
fontSize: 14, fontSize: 16,
paddingBottom: 8, lineHeight: 20,
paddingLeft: 12, paddingHorizontal: 12,
paddingRight: 12, paddingTop: 12,
paddingTop: 8, paddingBottom: 6,
maxHeight: 150, minHeight: 38,
}, },
inputContainer: { inputContainer: {
flex: 1, flex: 1,
flexDirection: 'column', flexDirection: 'column',
backgroundColor: theme.centerChannelBg,
marginRight: 10,
marginLeft: 10,
}, },
inputContentContainer: { inputContentContainer: {
alignItems: 'stretch', alignItems: 'stretch',
@@ -949,7 +976,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
alignItems: 'flex-end', alignItems: 'flex-end',
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
paddingVertical: 4, paddingBottom: 8,
backgroundColor: theme.centerChannelBg, backgroundColor: theme.centerChannelBg,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: changeOpacity(theme.centerChannelColor, 0.20), borderTopColor: changeOpacity(theme.centerChannelColor, 0.20),

View File

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {memo} from 'react'; import React, {memo} from 'react';
import {Platform, View} from 'react-native'; import {View} from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import TouchableWithFeedback from 'app/components/touchable_with_feedback'; import TouchableWithFeedback from 'app/components/touchable_with_feedback';
@@ -18,19 +18,15 @@ function SendButton(props) {
PaperPlane = require('app/components/paper_plane').default; PaperPlane = require('app/components/paper_plane').default;
} }
const icon = (
<PaperPlane
height={13}
width={15}
color={theme.buttonColor}
/>
);
if (props.disabled) { if (props.disabled) {
return ( return (
<View style={style.sendButtonContainer}> <View style={style.sendButtonContainer}>
<View style={[style.sendButton, style.disableButton]}> <View style={[style.sendButton, style.disableButton]}>
{icon} <PaperPlane
height={16}
width={19}
color={changeOpacity(theme.buttonColor, 0.5)}
/>
</View> </View>
</View> </View>
); );
@@ -43,7 +39,11 @@ function SendButton(props) {
type={'opacity'} type={'opacity'}
> >
<View style={style.sendButton}> <View style={style.sendButton}>
{icon} <PaperPlane
height={16}
width={19}
color={theme.buttonColor}
/>
</View> </View>
</TouchableWithFeedback> </TouchableWithFeedback>
); );
@@ -62,20 +62,15 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
}, },
sendButtonContainer: { sendButtonContainer: {
justifyContent: 'flex-end', justifyContent: 'flex-end',
paddingHorizontal: 5, paddingRight: 8,
paddingVertical: Platform.select({
android: 8,
ios: 2,
}),
}, },
sendButton: { sendButton: {
backgroundColor: theme.buttonBg, backgroundColor: theme.buttonBg,
borderRadius: 4, borderRadius: 4,
height: 28, height: 32,
width: 72, width: 80,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingLeft: 3,
}, },
}; };
}); });

View File

@@ -5,3 +5,4 @@ export const MAX_FILE_COUNT = 5;
export const IS_REACTION_REGEX = /(^\+:([^:\s]*):)$/i; export const IS_REACTION_REGEX = /(^\+:([^:\s]*):)$/i;
export const INSERT_TO_DRAFT = 'insert_to_draft'; export const INSERT_TO_DRAFT = 'insert_to_draft';
export const INSERT_TO_COMMENT = 'insert_to_comment'; export const INSERT_TO_COMMENT = 'insert_to_comment';
export const ICON_SIZE = 24;

View File

@@ -121,6 +121,7 @@ export default {
NotificationLevels, NotificationLevels,
SidebarSectionTypes, SidebarSectionTypes,
IOS_HORIZONTAL_LANDSCAPE: 44, IOS_HORIZONTAL_LANDSCAPE: 44,
INPUT_LINE_HEIGHT: 17, INPUT_LINE_HEIGHT: 20,
INPUT_VERTICAL_PADDING: 16, INPUT_VERTICAL_PADDING: 18,
INPUT_INITIAL_HEIGHT: 38,
}; };

View File

@@ -18,6 +18,7 @@ if (Platform.OS === 'android') {
if (__DEV__) { if (__DEV__) {
YellowBox.ignoreWarnings([ YellowBox.ignoreWarnings([
'Warning: componentWillReceiveProps', 'Warning: componentWillReceiveProps',
'`-[RCTRootView cancelTouches]`',
// Hide warnings caused by React Native (https://github.com/facebook/react-native/issues/20841) // Hide warnings caused by React Native (https://github.com/facebook/react-native/issues/20841)
'Require cycle: node_modules/react-native/Libraries/Network/fetch.js', 'Require cycle: node_modules/react-native/Libraries/Network/fetch.js',