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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ exports[`PostTextBox should match, full snapshot 1`] = `
"borderTopWidth": 1,
"flexDirection": "row",
"justifyContent": "center",
"paddingVertical": 4,
"paddingBottom": 8,
},
null,
]
@@ -36,11 +36,8 @@ exports[`PostTextBox should match, full snapshot 1`] = `
style={
Array [
Object {
"backgroundColor": "#ffffff",
"flex": 1,
"flexDirection": "column",
"marginLeft": 10,
"marginRight": 10,
},
]
}
@@ -61,100 +58,249 @@ exports[`PostTextBox should match, full snapshot 1`] = `
style={
Object {
"color": "#3d3c40",
"fontSize": 14,
"fontSize": 16,
"lineHeight": 20,
"maxHeight": 150,
"paddingBottom": 8,
"paddingLeft": 12,
"paddingRight": 12,
"paddingTop": 8,
"minHeight": 38,
"paddingBottom": 6,
"paddingHorizontal": 12,
"paddingTop": 12,
}
}
underlineColorAndroid="transparent"
value=""
/>
<Connect(FileUploadPreview)
files={Array []}
rootId=""
/>
<View
style={
Object {
"alignItems": "center",
"display": "flex",
"flexDirection": "row",
"justifyContent": "space-between",
}
}
>
<React.Fragment>
<Connect(FileUploadPreview)
files={Array []}
rootId=""
/>
<View
style={
Object {
"alignItems": "center",
"display": "flex",
"flexDirection": "row",
"justifyContent": "space-between",
}
}
>
<TouchableOpacity
activeOpacity={0.2}
disabled={false}
onPress={[Function]}
<View
style={
Object {
"paddingLeft": 10,
"paddingRight": 10,
"display": "flex",
"flexDirection": "row",
"height": 44,
}
}
>
<Icon
allowFontScaling={false}
color="#3d3c40"
name="at"
size={20}
/>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.2}
disabled={false}
onPress={[Function]}
style={
Object {
"paddingLeft": 10,
"paddingRight": 10,
}
}
>
<Image
source={
<TouchableOpacity
activeOpacity={0.2}
disabled={false}
onPress={[Function]}
style={
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={
Array [
Object {
"alignItems": "center",
"justifyContent": "center",
"padding": 10,
}
}
>
<Image
source={
Object {
"height": 20,
"opacity": 1,
"tintColor": "#3d3c40",
"width": 20,
},
]
"testUri": "../../../dist/assets/images/icons/slash-forward-box.png",
}
}
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>
<FileUploadButton
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]}
<ImageUploadButton
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 []}
/>
<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={
Object {
"awayIndicator": "#ffbc42",
@@ -184,131 +330,9 @@ exports[`PostTextBox should match, full snapshot 1`] = `
"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>
<SendButton
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>
</React.Fragment>
</ScrollView>
</View>
</React.Fragment>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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