diff --git a/app/components/conditionalWrapper.js b/app/components/conditionalWrapper.js deleted file mode 100644 index bb5dd9f1f..000000000 --- a/app/components/conditionalWrapper.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -/* eslint-disable no-confusing-arrow */ -export const ConditionalWrapper = ({conditional, wrapper, children}) => conditional ? wrapper(children) : children; diff --git a/app/components/file_upload_preview/file_upload_item/file_upload_item.js b/app/components/file_upload_preview/file_upload_item/file_upload_item.js index 9270a57e6..19d9b4613 100644 --- a/app/components/file_upload_preview/file_upload_item/file_upload_item.js +++ b/app/components/file_upload_preview/file_upload_item/file_upload_item.js @@ -168,12 +168,10 @@ export default class FileUploadItem extends PureComponent { const realFill = Number(fill.toFixed(0)); return ( - - - - {`${realFill}%`} - - + + + {`${realFill}%`} + ); }; @@ -204,8 +202,8 @@ export default class FileUploadItem extends PureComponent { ); @@ -227,13 +225,12 @@ export default class FileUploadItem extends PureComponent { {file.loading && !file.failed && {this.renderProgress} @@ -254,51 +251,33 @@ export default class FileUploadItem extends PureComponent { const getStyleSheet = makeStyleSheetFromTheme((theme) => ({ preview: { - paddingTop: 12, + paddingTop: 5, marginLeft: 12, }, previewContainer: { - height: 64, - width: 64, - elevation: 10, + height: 56, + width: 56, borderRadius: 4, }, - progressCircle: { - alignItems: 'center', - height: '100%', - justifyContent: 'center', - width: '100%', - }, progressCircleContent: { alignItems: 'center', - backgroundColor: 'rgba(0, 0, 0, 0.4)', - height: 64, + backgroundColor: 'rgba(0, 0, 0, 0.7)', + height: 56, + width: 56, justifyContent: 'center', position: 'absolute', - width: 64, - }, - progressCirclePercentage: { - alignItems: 'center', - flex: 1, - justifyContent: 'center', - }, - progressContent: { - alignItems: 'center', - height: '100%', - justifyContent: 'center', - left: 0, - position: 'absolute', - width: '100%', + borderRadius: 4, }, progressText: { color: 'white', - fontSize: 18, + fontSize: 11, + fontWeight: 'bold', }, filePreview: { borderColor: changeOpacity(theme.centerChannelColor, 0.15), - borderRadius: 5, + borderRadius: 4, borderWidth: 1, - width: 64, - height: 64, + width: 56, + height: 56, }, })); diff --git a/app/components/file_upload_preview/file_upload_preview.js b/app/components/file_upload_preview/file_upload_preview.js index 01d460f57..b114e9057 100644 --- a/app/components/file_upload_preview/file_upload_preview.js +++ b/app/components/file_upload_preview/file_upload_preview.js @@ -8,6 +8,7 @@ import { ScrollView, Text, View, + Platform, } from 'react-native'; import * as Animatable from 'react-native-animatable'; @@ -18,9 +19,7 @@ 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 showFiles = {opacity: 1, height: 68}; const hideFiles = {opacity: 0, height: 0}; const hideError = {height: 0}; @@ -93,14 +92,9 @@ export default class FileUploadPreview extends PureComponent { handleFileMaxWarning = () => { this.setState({showFileMaxWarning: true}); if (this.errorRef.current) { - this.makeErrorVisible(true, 20, null, () => { - this.errorRef.current.transition(initial, final, 350, 'ease-in'); - }); + this.makeErrorVisible(true, 20); setTimeout(() => { - this.makeErrorVisible(false, 20, 350, () => { - this.errorRef.current.transition(final, initial, 350, 'ease-out'); - this.clearErrorsFromState(400); - }); + this.makeErrorVisible(false, 20); }, 5000); } }; @@ -108,29 +102,20 @@ export default class FileUploadPreview extends PureComponent { handleFileSizeWarning = (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'); - }); + this.setState({fileSizeWarning: message.replace(': ', ':\n')}); + this.makeErrorVisible(true, 40); } else { - this.makeErrorVisible(false, 42, 350, () => { - this.errorRef.current.transition(final, initial, 350, 'ease-out'); - this.clearErrorsFromState(400); - }); + this.makeErrorVisible(false, 20); } } }; - makeErrorVisible = (visible, height, delay, callback) => { + makeErrorVisible = (visible, height) => { if (this.errorContainerRef.current) { if (visible) { - this.errorContainerRef.current.transition(hideError, {height}, 100); - setTimeout(callback, delay || 150); + this.errorContainerRef.current.transition(hideError, {height}, 200, 'ease-out'); } else { - callback(); - setTimeout(() => { - this.errorContainerRef.current.transition({height}, hideError, 300); - }, delay || 150); + this.errorContainerRef.current.transition({height}, hideError, 200, 'ease-in'); } } } @@ -153,15 +138,18 @@ export default class FileUploadPreview extends PureComponent { render() { const {fileSizeWarning, showFileMaxWarning} = this.state; - const style = getStyleSheet(this.props.theme); + const {theme, files} = this.props; + const style = getStyleSheet(theme); + const fileContainerStyle = { + paddingBottom: files.length ? 5 : 0, + }; return ( { display: 'flex', flexDirection: 'row', height: 0, - alignItems: 'center', }, errorContainer: { height: 0, }, errorTextContainer: { - marginTop: 5, + marginTop: Platform.select({ + ios: 4, + android: 2, + }), marginHorizontal: 12, - opacity: 0, flex: 1, }, scrollView: { diff --git a/app/components/file_upload_preview/file_upload_remove.js b/app/components/file_upload_preview/file_upload_remove.js index 51dbcfde8..f39c57a87 100644 --- a/app/components/file_upload_preview/file_upload_remove.js +++ b/app/components/file_upload_preview/file_upload_remove.js @@ -3,7 +3,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {View} from 'react-native'; +import {View, Platform} from 'react-native'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme'; @@ -37,7 +37,8 @@ export default class FileUploadRemove extends PureComponent { @@ -50,17 +51,26 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { tappableContainer: { position: 'absolute', elevation: 11, - top: -2, - right: -16, + top: -5, + right: -10, width: 32, height: 32, }, removeButton: { - borderRadius: 20, + borderRadius: 12, alignSelf: 'center', - paddingTop: 6, - paddingHorizontal: 1, + marginTop: Platform.select({ + ios: 5.4, + android: 4.75, + }), backgroundColor: theme.centerChannelBg, }, + removeIcon: { + position: 'relative', + top: Platform.select({ + ios: 1, + android: 0, + }), + }, }; }); diff --git a/app/components/file_upload_preview/file_upload_retry.js b/app/components/file_upload_preview/file_upload_retry.js index e073cefea..781ecf052 100644 --- a/app/components/file_upload_preview/file_upload_retry.js +++ b/app/components/file_upload_preview/file_upload_retry.js @@ -29,7 +29,7 @@ export default class FileUploadRetry extends PureComponent { > @@ -45,5 +45,6 @@ const style = StyleSheet.create({ width: '100%', alignItems: 'center', justifyContent: 'center', + borderRadius: 4, }, }); diff --git a/app/components/pasteable_text_input/__snapshots__/index.test.js.snap b/app/components/pasteable_text_input/__snapshots__/index.test.js.snap index c5d9cec42..be4db3f1b 100644 --- a/app/components/pasteable_text_input/__snapshots__/index.test.js.snap +++ b/app/components/pasteable_text_input/__snapshots__/index.test.js.snap @@ -1,18 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`PasteableTextInput should render pasteable text input 1`] = ` - - - My Text - - + My Text + `; diff --git a/app/components/pasteable_text_input/index.js b/app/components/pasteable_text_input/index.js index abdb0fd98..0f3fe44d2 100644 --- a/app/components/pasteable_text_input/index.js +++ b/app/components/pasteable_text_input/index.js @@ -3,10 +3,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Animated, Easing, NativeEventEmitter, NativeModules, Platform, TextInput} from 'react-native'; +import {TextInput, NativeEventEmitter, NativeModules} from 'react-native'; import CustomTextInput from './custom_text_input'; -import {ConditionalWrapper} from 'app/components/conditionalWrapper'; -import {ViewTypes} from 'app/constants'; const {OnPasteEventManager} = NativeModules; const OnPasteEventEmitter = new NativeEventEmitter(OnPasteEventManager); @@ -18,8 +16,6 @@ export class PasteableTextInput extends React.PureComponent { forwardRef: PropTypes.any, } - inputHeight = new Animated.Value(ViewTypes.INPUT_INITIAL_HEIGHT); - componentDidMount() { this.subscription = OnPasteEventEmitter.addListener('onPaste', this.onPaste); } @@ -35,52 +31,14 @@ export class PasteableTextInput extends React.PureComponent { return onPaste?.(null, event); } - animateHeight = (event) => { - if (Platform.OS === 'ios') { - const {height} = event.nativeEvent.contentSize; - const {style, value} = this.props; - const newHeight = Math.min(style.maxHeight, height + ViewTypes.INPUT_VERTICAL_PADDING); - - 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) => { - return ( - - {children} - - ); - } - render() { const {forwardRef, ...props} = this.props; return ( - - - + ); } } diff --git a/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap index 34227db22..0ede5d69e 100644 --- a/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap +++ b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap @@ -14,7 +14,7 @@ exports[`PostTextBox should match, full snapshot 1`] = ` "borderTopWidth": 1, "flexDirection": "row", "justifyContent": "center", - "paddingBottom": 8, + "paddingBottom": 2, }, null, ] @@ -24,6 +24,7 @@ exports[`PostTextBox should match, full snapshot 1`] = ` contentContainerStyle={ Object { "alignItems": "stretch", + "paddingTop": 7, } } disableScrollViewPanResponder={true} @@ -50,6 +51,7 @@ exports[`PostTextBox should match, full snapshot 1`] = ` keyboardType="default" multiline={true} onChangeText={[Function]} + onContentSizeChange={null} onEndEditing={[Function]} onPaste={[Function]} onSelectionChange={[Function]} @@ -58,13 +60,13 @@ exports[`PostTextBox should match, full snapshot 1`] = ` style={ Object { "color": "#3d3c40", - "fontSize": 16, + "fontSize": 15, "lineHeight": 20, "maxHeight": 150, - "minHeight": 38, + "minHeight": 30, "paddingBottom": 6, "paddingHorizontal": 12, - "paddingTop": 12, + "paddingTop": 6, } } underlineColorAndroid="transparent" @@ -82,6 +84,7 @@ exports[`PostTextBox should match, full snapshot 1`] = ` "display": "flex", "flexDirection": "row", "justifyContent": "space-between", + "paddingBottom": 1, } } > diff --git a/app/components/post_textbox/post_textbox_base.js b/app/components/post_textbox/post_textbox_base.js index 0b3209284..5793486f6 100644 --- a/app/components/post_textbox/post_textbox_base.js +++ b/app/components/post_textbox/post_textbox_base.js @@ -8,8 +8,6 @@ import { AppState, BackHandler, findNodeHandle, - Image, - InteractionManager, Keyboard, NativeModules, Platform, @@ -17,6 +15,7 @@ import { TouchableOpacity, ScrollView, View, + Image, } from 'react-native'; import {intlShape} from 'react-intl'; import RNFetchBlob from 'rn-fetch-blob'; @@ -51,6 +50,8 @@ import { } from 'app/utils/theme'; const {RNTextInputReset} = NativeModules; +const INPUT_LINE_HEIGHT = 20; +const EXTRA_INPUT_PADDING = 3; export default class PostTextBoxBase extends PureComponent { static propTypes = { @@ -122,6 +123,7 @@ export default class PostTextBoxBase extends PureComponent { channelId: props.channelId, channelTimezoneCount: 0, longMessageAlertShown: false, + extraInputPadding: 0, }; } @@ -651,10 +653,26 @@ export default class PostTextBoxBase extends PureComponent { actions.handleClearFiles(channelId, rootId); } - InteractionManager.runAfterInteractions(() => { + if (Platform.OS === 'ios') { + // On iOS, if the PostTextbox height increases from its + // initial height (due to a multiline post or a post whose + // message wraps, for example), then when the text is cleared + // the PostTextbox height decrease will be animated. This + // animation in conjunction with the PostList animation as it + // receives the newly created post is causing issues in the iOS + // PostList component as it fails to properly react to its content + // size changes. While a proper fix is determined for the PostList + // component, a small delay in triggering the height decrease + // animation gives the PostList enough time to first handle content + // size changes from the new post. + setTimeout(() => { + this.handleTextChange(''); + this.setState({sendingMessage: false}); + }, 250); + } else { this.handleTextChange(''); this.setState({sendingMessage: false}); - }); + } this.changeDraft(''); @@ -838,6 +856,16 @@ export default class PostTextBoxBase extends PureComponent { } }; + handleInputSizeChange = ({nativeEvent: {contentSize}}) => { + const {extraInputPadding} = this.state; + const numLines = contentSize.height / INPUT_LINE_HEIGHT; + if (numLines >= 2 && extraInputPadding !== EXTRA_INPUT_PADDING) { + this.setState({extraInputPadding: EXTRA_INPUT_PADDING}); + } else if (numLines < 2 && extraInputPadding !== 0) { + this.setState({extraInputPadding: 0}); + } + } + renderDeactivatedChannel = () => { const {intl} = this.context; const style = getStyleSheet(this.props.theme); @@ -861,7 +889,7 @@ export default class PostTextBoxBase extends PureComponent { return this.archivedView(theme, style); } - const {value} = this.state; + const {value, extraInputPadding} = this.state; const textValue = channelIsLoading ? '' : value; const placeholder = this.getPlaceHolder(); @@ -871,6 +899,12 @@ export default class PostTextBoxBase extends PureComponent { maxHeight = 88; } + const inputStyle = {}; + if (extraInputPadding) { + inputStyle.paddingBottom = style.input.paddingBottom + extraInputPadding; + inputStyle.paddingTop = style.input.paddingTop + extraInputPadding; + } + return ( {!channelIsReadOnly && @@ -947,6 +982,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', + paddingBottom: Platform.select({ + ios: 1, + android: 2, + }), }, slashIcon: { width: ICON_SIZE, @@ -969,12 +1008,18 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }, input: { color: theme.centerChannelColor, - fontSize: 16, - lineHeight: 20, + fontSize: 15, + lineHeight: INPUT_LINE_HEIGHT, paddingHorizontal: 12, - paddingTop: 12, - paddingBottom: 6, - minHeight: 38, + paddingTop: Platform.select({ + ios: 6, + android: 8, + }), + paddingBottom: Platform.select({ + ios: 6, + android: 2, + }), + minHeight: 30, }, inputContainer: { flex: 1, @@ -982,12 +1027,16 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }, inputContentContainer: { alignItems: 'stretch', + paddingTop: Platform.select({ + ios: 7, + android: 0, + }), }, inputWrapper: { alignItems: 'flex-end', flexDirection: 'row', justifyContent: 'center', - paddingBottom: 8, + paddingBottom: 2, backgroundColor: theme.centerChannelBg, borderTopWidth: 1, borderTopColor: changeOpacity(theme.centerChannelColor, 0.20), diff --git a/app/constants/view.js b/app/constants/view.js index 34791a1a9..9bb077a31 100644 --- a/app/constants/view.js +++ b/app/constants/view.js @@ -121,7 +121,4 @@ export default { NotificationLevels, SidebarSectionTypes, IOS_HORIZONTAL_LANDSCAPE: 44, - INPUT_LINE_HEIGHT: 20, - INPUT_VERTICAL_PADDING: 18, - INPUT_INITIAL_HEIGHT: 38, };