Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a button to expand the composer to full screen when the input has 3+ lines #9031

Merged
merged 60 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
ea2c046
Permanent full compose initial
neil-marcellini May 10, 2022
ae6f5d6
Small cleanup before making the size dynamic
neil-marcellini May 10, 2022
f2bded2
Expand and collapse composer with 3+ lines initial
neil-marcellini May 10, 2022
52b38f6
Update ReportActionsView on composer size changes
neil-marcellini May 16, 2022
06ea730
Fix chat scroll and composer expand and collapse
neil-marcellini May 16, 2022
238f975
Undo changes to ReportActionsView
neil-marcellini May 16, 2022
41b1903
Close full screen composer on send
neil-marcellini May 16, 2022
bc60532
Immediately hide the expand button on send
neil-marcellini May 16, 2022
c8ed9c7
Hide the expand button when deleting the 3rd line
neil-marcellini May 16, 2022
8d72922
Remove console log
neil-marcellini May 16, 2022
e9776df
Remove duplicate isFullComposerAvailable state
neil-marcellini May 16, 2022
a721395
Always reset fullComposerAvailable on send
neil-marcellini May 16, 2022
4fdb698
Keyboard avoiding ios full composer with debug
neil-marcellini May 17, 2022
3f4ae51
Remove red debug borders
neil-marcellini May 17, 2022
1d5fdf7
Fix button flicker when updating number of lines
neil-marcellini May 17, 2022
7416d80
Clean up RNTextInput props
neil-marcellini May 17, 2022
9f07fdd
Set a max height on the non-full mobile composer
neil-marcellini May 17, 2022
8150185
Remove border from the composer size buttons
neil-marcellini May 18, 2022
5051477
Keep plus button at the bottom as lines are added
neil-marcellini May 18, 2022
569981c
Combine mobile Composer implementations
neil-marcellini May 18, 2022
3de3993
Move comment
neil-marcellini May 18, 2022
cbf4eb4
Use the expand Expensicon to expand the composer
neil-marcellini May 19, 2022
cd6b497
Give isComposerFullSize a default prop
neil-marcellini May 19, 2022
90dd68b
Merge branch 'main' of github.com:Expensify/App into neil-full-compose
neil-marcellini May 19, 2022
eae4d41
Use a collapse icon to collapse the full composer
neil-marcellini May 24, 2022
ca39644
Default isFullComposerAvailable to false if unset
neil-marcellini May 24, 2022
56ca90c
Fix composer placeholder centering on android
neil-marcellini May 24, 2022
99c45c1
Fix the emoji picker with the full composer
neil-marcellini May 25, 2022
c09b08f
Keep the composer full size on keyboard dismissal
neil-marcellini May 25, 2022
63c3f86
Add expand and collapse translations for tooltip
neil-marcellini May 27, 2022
1a26946
Allow text to take up the full composer
neil-marcellini May 27, 2022
ec73a33
Allow unfocused full composer to scroll on web
neil-marcellini May 27, 2022
80fbbc1
Fix full composer input align to bottom
neil-marcellini May 27, 2022
08f041a
Cleanup useless display none on flatlist
neil-marcellini May 27, 2022
89fe8d5
Use sizing.h100 style
neil-marcellini May 27, 2022
6e7b6e1
Initial fix for mobile web full composer height
neil-marcellini Jun 1, 2022
4179675
Keep input focus on mobile web when expanding
neil-marcellini Jun 1, 2022
c6110d8
Mobile web full composer working including header
neil-marcellini Jun 1, 2022
a20abd2
Check for window.visualViewport vs window
neil-marcellini Jun 1, 2022
08bd11c
Remove arbitrary composer focus on expand
neil-marcellini Jun 2, 2022
8abaec3
Fix android full composer bottom outline
neil-marcellini Jun 2, 2022
50fc8f9
Fix mobile composer collapse animation on send
neil-marcellini Jun 2, 2022
03da55f
Merge branch 'main' of github.com:Expensify/App into neil-full-compose
neil-marcellini Jun 2, 2022
5599c5a
Revert "Fix android full composer bottom outline"
neil-marcellini Jun 2, 2022
3e50996
Revert "Fix mobile composer collapse animation on send"
neil-marcellini Jun 3, 2022
9196625
Transparent composer to fix android outline
neil-marcellini Jun 3, 2022
e7d9078
Revert "Transparent composer to fix android outline"
neil-marcellini Jun 3, 2022
5cecb2c
Fix android bottom border with a spacing wrapper
neil-marcellini Jun 3, 2022
b6c1751
Fix composer collapse on send with spacing wrapper
neil-marcellini Jun 3, 2022
1f66c3a
Match button and composer vertical padding
neil-marcellini Jun 3, 2022
36db399
Simplify setting isFulComposerAvailable
neil-marcellini Jun 6, 2022
8b221c7
Fix comment typo and formatting
neil-marcellini Jun 6, 2022
e5a30e0
Extract updateIsFullComposerAvailable into util
neil-marcellini Jun 6, 2022
393efe1
Mobile composer separate files with shared utils
neil-marcellini Jun 6, 2022
b5888fa
Remove useless onChange prop from mobile composers
neil-marcellini Jun 6, 2022
c694946
Merge main to fix conflicts
neil-marcellini Jun 10, 2022
5d7c80f
Add a JSDoc to Report.setIsComposerFullSize
neil-marcellini Jun 10, 2022
32573a6
Switch to merge for setIsComposerFullSize
neil-marcellini Jun 10, 2022
0b5ecd3
Add composer max lines constants
neil-marcellini Jun 10, 2022
c1f2af8
Move FULL_COMPOSER_MIN_LINES under COMPOSER key
neil-marcellini Jun 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions assets/images/collapse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions assets/images/expand.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,14 @@ const CONST = {
MAX_ROOM_NAME_LENGTH: 80,
LAST_MESSAGE_TEXT_MAX_LENGTH: 80,
},
COMPOSER: {
MAX_LINES: 16,
MAX_LINES_SMALL_SCREEN: 6,
MAX_LINES_FULL: -1,

// The minimum number of typed lines needed to enable the full screen composer
FULL_COMPOSER_MIN_LINES: 3,
},
MODAL: {
MODAL_TYPE: {
CONFIRM: 'confirm',
Expand Down
1 change: 1 addition & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export default {
REPORT_IOUS: 'reportIOUs_',
POLICY: 'policy_',
REPORTS_WITH_DRAFT: 'reportWithDraft_',
REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_',
},

// Indicates which locale should be used
Expand Down
51 changes: 41 additions & 10 deletions src/components/Composer/index.android.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import React from 'react';
import {StyleSheet} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import RNTextInput from '../RNTextInput';
import themeColors from '../../styles/themes/default';
import CONST from '../../CONST';

/**
* On native layers we like to have the Text Input not focused so the user can read new chats without they keyboard in
* the way of the view
* On Android, the selection prop is required on the TextInput but this prop has issues on IOS
* https://github.com/facebook/react-native/issues/29063
*/
import * as ComposerUtils from '../../libs/ComposerUtils';

const propTypes = {
/** If the input should clear, it actually gets intercepted instead of .clear() */
Expand All @@ -29,6 +24,25 @@ const propTypes = {
/** Prevent edits and interactions like focus for this input. */
isDisabled: PropTypes.bool,

/** Selection Object */
selection: PropTypes.shape({
start: PropTypes.number,
end: PropTypes.number,
}),

/** Whether the full composer can be opened */
isFullComposerAvailable: PropTypes.bool,

/** Allow the full composer to be opened */
setIsFullComposerAvailable: PropTypes.func,

/** Whether the composer is full size */
isComposerFullSize: PropTypes.bool.isRequired,

/** General styles to apply to the text input */
// eslint-disable-next-line react/forbid-prop-types
style: PropTypes.any,

};

const defaultProps = {
Expand All @@ -37,9 +51,24 @@ const defaultProps = {
autoFocus: false,
isDisabled: false,
forwardedRef: null,
selection: {
start: 0,
end: 0,
},
isFullComposerAvailable: false,
setIsFullComposerAvailable: () => {},
style: null,
};

class Composer extends React.Component {
constructor(props) {
super(props);

this.state = {
propStyles: StyleSheet.flatten(this.props.style),
};
}

componentDidMount() {
// This callback prop is used by the parent component using the constructor to
// get a ref to the inner textInput element e.g. if we do
Expand Down Expand Up @@ -67,17 +96,19 @@ class Composer extends React.Component {
autoComplete="off"
placeholderTextColor={themeColors.placeholderText}
ref={el => this.textInput = el}
maxHeight={CONST.COMPOSER_MAX_HEIGHT}
maxHeight={this.props.isComposerFullSize ? '100%' : CONST.COMPOSER_MAX_HEIGHT}
onContentSizeChange={e => ComposerUtils.updateNumberOfLines(this.props, e)}
rejectResponderTermination={false}
editable={!this.props.isDisabled}
textAlignVertical="center"
style={this.state.propStyles}
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...this.props}
editable={!this.props.isDisabled}
/>
);
}
}

Composer.displayName = 'Composer';
Composer.propTypes = propTypes;
Composer.defaultProps = defaultProps;

Expand Down
45 changes: 35 additions & 10 deletions src/components/Composer/index.ios.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import React from 'react';
import {StyleSheet} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import RNTextInput from '../RNTextInput';
import themeColors from '../../styles/themes/default';
import CONST from '../../CONST';

/**
* On native layers we like to have the Text Input not focused so the user can read new chats without they keyboard in
* the way of the view
* On Android, the selection prop is required on the TextInput but this prop has issues on IOS
* https://github.com/facebook/react-native/issues/29063
*/
import * as ComposerUtils from '../../libs/ComposerUtils';

const propTypes = {
/** If the input should clear, it actually gets intercepted instead of .clear() */
Expand All @@ -35,6 +30,19 @@ const propTypes = {
end: PropTypes.number,
}),

/** Whether the full composer can be opened */
isFullComposerAvailable: PropTypes.bool,

/** Allow the full composer to be opened */
setIsFullComposerAvailable: PropTypes.func,

/** Whether the composer is full size */
isComposerFullSize: PropTypes.bool.isRequired,

/** General styles to apply to the text input */
// eslint-disable-next-line react/forbid-prop-types
style: PropTypes.any,

};

const defaultProps = {
Expand All @@ -47,9 +55,20 @@ const defaultProps = {
start: 0,
end: 0,
},
isFullComposerAvailable: false,
setIsFullComposerAvailable: () => {},
style: null,
};

class Composer extends React.Component {
constructor(props) {
super(props);

this.state = {
propStyles: StyleSheet.flatten(this.props.style),
};
}

componentDidMount() {
// This callback prop is used by the parent component using the constructor to
// get a ref to the inner textInput element e.g. if we do
Expand All @@ -72,18 +91,24 @@ class Composer extends React.Component {
}

render() {
// Selection Property not worked in IOS properly, So removed from props.
// On native layers we like to have the Text Input not focused so the
// user can read new chats without the keyboard in the way of the view.
// On Android, the selection prop is required on the TextInput but this prop has issues on IOS
// https://github.com/facebook/react-native/issues/29063
const propsToPass = _.omit(this.props, 'selection');
return (
<RNTextInput
autoComplete="off"
placeholderTextColor={themeColors.placeholderText}
ref={el => this.textInput = el}
maxHeight={CONST.COMPOSER_MAX_HEIGHT}
maxHeight={this.props.isComposerFullSize ? '100%' : CONST.COMPOSER_MAX_HEIGHT}
onContentSizeChange={e => ComposerUtils.updateNumberOfLines(this.props, e)}
rejectResponderTermination={false}
editable={!this.props.isDisabled}
textAlignVertical="center"
style={this.state.propStyles}
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...propsToPass}
editable={!this.props.isDisabled}
/>
);
}
Expand Down
44 changes: 22 additions & 22 deletions src/components/Composer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import withLocalize, {withLocalizePropTypes} from '../withLocalize';
import Growl from '../../libs/Growl';
import themeColors from '../../styles/themes/default';
import CONST from '../../CONST';
import updateIsFullComposerAvailable from '../../libs/ComposerUtils/updateIsFullComposerAvailable';
import getNumberOfLines from '../../libs/ComposerUtils/index';

const propTypes = {
/** Maximum number of lines in the text input */
Expand Down Expand Up @@ -63,6 +65,12 @@ const propTypes = {
end: PropTypes.number,
}),

/** Whether the full composer can be opened */
isFullComposerAvailable: PropTypes.bool,

/** Allow the full composer to be opened */
setIsFullComposerAvailable: PropTypes.func,

...withLocalizePropTypes,
};

Expand All @@ -86,6 +94,8 @@ const defaultProps = {
start: 0,
end: 0,
},
isFullComposerAvailable: false,
setIsFullComposerAvailable: () => {},
};

const IMAGE_EXTENSIONS = {
Expand Down Expand Up @@ -155,7 +165,8 @@ class Composer extends React.Component {
this.setState({numberOfLines: 1});
this.props.onClear();
}
if (prevProps.defaultValue !== this.props.defaultValue) {
if (prevProps.defaultValue !== this.props.defaultValue
|| prevProps.isComposerFullSize !== this.props.isComposerFullSize) {
this.updateNumberOfLines();
}

Expand All @@ -178,22 +189,6 @@ class Composer extends React.Component {
this.textInput.removeEventListener('wheel', this.handleWheel);
}

/**
* Calculates the max number of lines the text input can have
*
* @param {Number} lineHeight
* @param {Number} paddingTopAndBottom
* @param {Number} scrollHeight
*
* @returns {Number}
*/
getNumberOfLines(lineHeight, paddingTopAndBottom, scrollHeight) {
const maxLines = this.props.maxLines;
let newNumberOfLines = Math.ceil((scrollHeight - paddingTopAndBottom) / lineHeight);
newNumberOfLines = maxLines <= 0 ? newNumberOfLines : Math.min(newNumberOfLines, maxLines);
return newNumberOfLines;
}

/**
* Handles all types of drag-N-drop events on the composer
*
Expand Down Expand Up @@ -328,16 +323,21 @@ class Composer extends React.Component {
* divide by line height to get the total number of rows for the textarea.
*/
updateNumberOfLines() {
const computedStyle = window.getComputedStyle(this.textInput);
const lineHeight = parseInt(computedStyle.lineHeight, 10) || 20;
const paddingTopAndBottom = parseInt(computedStyle.paddingBottom, 10)
+ parseInt(computedStyle.paddingTop, 10);
// Hide the composer expand button so we can get an accurate reading of
// the height of the text input
this.props.setIsFullComposerAvailable(false);

// We have to reset the rows back to the minimum before updating so that the scrollHeight is not
// affected by the previous row setting. If we don't, rows will be added but not removed on backspace/delete.
this.setState({numberOfLines: 1}, () => {
const computedStyle = window.getComputedStyle(this.textInput);
const lineHeight = parseInt(computedStyle.lineHeight, 10) || 20;
const paddingTopAndBottom = parseInt(computedStyle.paddingBottom, 10)
+ parseInt(computedStyle.paddingTop, 10);
const numberOfLines = getNumberOfLines(this.props.maxLines, lineHeight, paddingTopAndBottom, this.textInput.scrollHeight);
updateIsFullComposerAvailable(this.props, numberOfLines);
this.setState({
numberOfLines: this.getNumberOfLines(lineHeight, paddingTopAndBottom, this.textInput.scrollHeight),
numberOfLines,
});
});
}
Expand Down
4 changes: 4 additions & 0 deletions src/components/Icon/Expensicons.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import CircleHourglass from '../../../assets/images/circle-hourglass.svg';
import Clipboard from '../../../assets/images/clipboard.svg';
import Close from '../../../assets/images/close.svg';
import ClosedSign from '../../../assets/images/closed-sign.svg';
import Collapse from '../../../assets/images/collapse.svg';
import Concierge from '../../../assets/images/concierge.svg';
import CreditCard from '../../../assets/images/creditcard.svg';
import DownArrow from '../../../assets/images/down.svg';
import Download from '../../../assets/images/download.svg';
import Emoji from '../../../assets/images/emoji.svg';
import Exclamation from '../../../assets/images/exclamation.svg';
import Exit from '../../../assets/images/exit.svg';
import Expand from '../../../assets/images/expand.svg';
import Eye from '../../../assets/images/eye.svg';
import EyeDisabled from '../../../assets/images/eye-disabled.svg';
import ExpensifyCard from '../../../assets/images/expensifycard.svg';
Expand Down Expand Up @@ -102,6 +104,7 @@ export {
Clipboard,
Close,
ClosedSign,
Collapse,
Concierge,
Connect,
CreditCard,
Expand All @@ -112,6 +115,7 @@ export {
Emoji,
Exclamation,
Exit,
Expand,
Eye,
EyeDisabled,
ExpensifyCard,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ export default {
localTime: ({user, time}) => `It's ${time} for ${user}`,
edited: '(edited)',
emoji: 'Emoji',
collapse: 'Collapse',
expand: 'Expand',
},
reportActionContextMenu: {
copyToClipboard: 'Copy to clipboard',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ export default {
localTime: ({user, time}) => `Son las ${time} para ${user}`,
edited: '(editado)',
emoji: 'Emoji',
collapse: 'Colapsar',
expand: 'Expandir',
},
reportActionContextMenu: {
copyToClipboard: 'Copiar al portapapeles',
Expand Down
17 changes: 17 additions & 0 deletions src/libs/ComposerUtils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Get the current number of lines in the composer
*
* @param {Number} maxLines
* @param {Number} lineHeight
* @param {Number} paddingTopAndBottom
* @param {Number} scrollHeight
*
* @returns {Number}
*/
function getNumberOfLines(maxLines, lineHeight, paddingTopAndBottom, scrollHeight) {
let newNumberOfLines = Math.ceil((scrollHeight - paddingTopAndBottom) / lineHeight);
newNumberOfLines = maxLines <= 0 ? newNumberOfLines : Math.min(newNumberOfLines, maxLines);
return newNumberOfLines;
}

export default getNumberOfLines;
Loading