Skip to content

Commit

Permalink
Merge pull request #1768 from tugbadogan/tugbadogan-create-IOU-amount
Browse files Browse the repository at this point in the history
Create IOU Amount step
  • Loading branch information
Julesssss authored Mar 18, 2021
2 parents 44ca84a + b582be8 commit fa6185b
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 19 deletions.
75 changes: 75 additions & 0 deletions src/components/BigNumberPad.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, {PureComponent} from 'react';
import {
Text, TouchableOpacity, View,
} from 'react-native';
import PropTypes from 'prop-types';
import styles from '../styles/styles';

const propTypes = {
// Callback to inform parent modal with key pressed
numberPressed: PropTypes.func.isRequired,
};

const padNumbers = [
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
['.', '0', '<'],
];

class BigNumberPad extends PureComponent {
/**
* Creates set of buttons for given row
*
* @param {number} row
* @returns {View}
*/
createNumberPadRow(row) {
const self = this;
const numberPadRow = padNumbers[row].map((column, index) => self.createNumberPadButton(row, index));
return (
<View key={row} style={[styles.flexRow, styles.mt3]}>
{numberPadRow}
</View>
);
}

/**
* Creates a button for given row and column
*
* @param {number} row
* @param {number} column
* @returns {View}
*/
createNumberPadButton(row, column) {
// Adding margin between buttons except first column to
// avoid unccessary space before the first column.
const marginLeft = column > 0 ? styles.ml3 : {};
return (
<TouchableOpacity
key={padNumbers[row][column]}
style={[styles.flex1, styles.button, marginLeft]}
onPress={() => this.props.numberPressed(padNumbers[row][column])}
>
<Text style={[styles.buttonText]}>
{padNumbers[row][column]}
</Text>
</TouchableOpacity>
);
}

render() {
const self = this;
const numberPad = padNumbers.map((row, index) => self.createNumberPadRow(index));
return (
<View style={[styles.flexColumn, styles.w100]}>
{numberPad}
</View>
);
}
}

BigNumberPad.propTypes = propTypes;
BigNumberPad.displayName = 'BigNumberPad';

export default BigNumberPad;
14 changes: 14 additions & 0 deletions src/components/TextInputFocusable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class TextInputFocusable extends React.Component {
}
if (prevProps.defaultValue !== this.props.defaultValue) {
this.updateNumberOfLines();
this.moveCursorToEnd();
}
}

Expand Down Expand Up @@ -198,6 +199,19 @@ class TextInputFocusable extends React.Component {
});
}

/**
* Move cursor to end by setting start and end
* to length of the input value.
*/
moveCursorToEnd() {
this.setState({
selection: {
start: this.props.defaultValue.length,
end: this.props.defaultValue.length,
},
});
}

focusInput() {
this.textInput.focus();
}
Expand Down
58 changes: 54 additions & 4 deletions src/pages/iou/IOUModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@ class IOUModal extends Component {

this.navigateToPreviousStep = this.navigateToPreviousStep.bind(this);
this.navigateToNextStep = this.navigateToNextStep.bind(this);
this.updateAmount = this.updateAmount.bind(this);
this.currencySelected = this.currencySelected.bind(this);

this.addParticipants = this.addParticipants.bind(this);
this.state = {
currentStepIndex: 0,
participants: [],
iouAmount: 42,
amount: '',
selectedCurrency: 'USD',
isAmountPageNextButtonDisabled: true,
};
}

Expand All @@ -60,7 +64,10 @@ class IOUModal extends Component {

getTitleForStep() {
if (this.state.currentStepIndex === 1) {
return `${this.props.hasMultipleParticipants ? 'Split' : 'Request'} $${this.state.iouAmount}`;
return `${this.props.hasMultipleParticipants ? 'Split' : 'Request'} $${this.state.amount}`;
}
if (steps[this.state.currentStepIndex] === Steps.IOUAmount) {
return this.props.hasMultipleParticipants ? 'Split Bill' : 'Request Money';
}
return steps[this.state.currentStepIndex] || '';
}
Expand Down Expand Up @@ -95,6 +102,42 @@ class IOUModal extends Component {
});
}

/**
* Update amount with number or Backspace pressed.
* Validate new amount with decimal number regex up to 6 digits and 2 decimal digit
*
* @param {String} buttonPressed
*/
updateAmount(buttonPressed) {
// Backspace button is pressed
if (buttonPressed === '<' || buttonPressed === 'Backspace') {
if (this.state.amount.length > 0) {
this.setState(prevState => ({
amount: prevState.amount.substring(0, prevState.amount.length - 1),
isAmountPageNextButtonDisabled: prevState.amount.length === 1,
}));
}
} else {
const decimalNumberRegex = new RegExp(/^\d{1,6}(\.\d{0,2})?$/, 'i');
const amount = this.state.amount + buttonPressed;
if (decimalNumberRegex.test(amount)) {
this.setState({
amount,
isAmountPageNextButtonDisabled: false,
});
}
}
}

/**
* Update the currency state
*
* @param {String} selectedCurrency
*/
currencySelected(selectedCurrency) {
this.setState({selectedCurrency});
}

render() {
const currentStep = steps[this.state.currentStepIndex];
return (
Expand Down Expand Up @@ -130,7 +173,14 @@ class IOUModal extends Component {
</View>
</View>
{currentStep === Steps.IOUAmount && (
<IOUAmountPage onStepComplete={this.navigateToNextStep} />
<IOUAmountPage
onStepComplete={this.navigateToNextStep}
numberPressed={this.updateAmount}
currencySelected={this.currencySelected}
amount={this.state.amount}
selectedCurrency={this.state.selectedCurrency}
isNextButtonDisabled={this.state.isAmountPageNextButtonDisabled}
/>
)}
{currentStep === Steps.IOUParticipants && (
<IOUParticipantsPage
Expand All @@ -144,7 +194,7 @@ class IOUModal extends Component {
<IOUConfirmPage
onConfirm={() => console.debug('create IOU report')}
participants={this.state.participants}
iouAmount={42}
iouAmount={this.state.amount}
/>
)}
</>
Expand Down
110 changes: 95 additions & 15 deletions src/pages/iou/steps/IOUAmountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,33 @@ import {withOnyx} from 'react-native-onyx';
import ONYXKEYS from '../../../ONYXKEYS';
import styles from '../../../styles/styles';
import themeColors from '../../../styles/themes/default';
import BigNumberPad from '../../../components/BigNumberPad';
import withWindowDimensions, {windowDimensionsPropTypes} from '../../../components/withWindowDimensions';
import TextInputFocusable from '../../../components/TextInputFocusable';

const propTypes = {
// Callback to inform parent modal of success
onStepComplete: PropTypes.func.isRequired,

// Callback to inform parent modal with key pressed
numberPressed: PropTypes.func.isRequired,

// Currency selection will be implemented later
// eslint-disable-next-line react/no-unused-prop-types
currencySelected: PropTypes.func.isRequired,

// User's currency preference
selectedCurrency: PropTypes.string.isRequired,

// Amount value entered by user
amount: PropTypes.string.isRequired,

// To disable/enable Next button based on amount validity
isNextButtonDisabled: PropTypes.bool.isRequired,

/* Window Dimensions Props */
...windowDimensionsPropTypes,

/* Onyx Props */

// Holds data related to IOU view state, rather than the underlying IOU data.
Expand All @@ -28,25 +50,83 @@ const propTypes = {
const defaultProps = {
iou: {},
};
class IOUAmountPage extends React.Component {
constructor(props) {
super(props);

this.state = {
textInputWidth: 0,
};
}

const IOUAmountPage = props => (
<View style={styles.pageWrapper}>
{props.iou.loading && <ActivityIndicator color={themeColors.text} />}
<TouchableOpacity
style={[styles.button, styles.w100, styles.mt5]}
onPress={props.onStepComplete}
>
<Text style={[styles.buttonText]}>
Next
</Text>
</TouchableOpacity>
</View>
);
componentDidMount() {
if (this.textInput) {
this.textInput.focus();
}
}

render() {
return (
<View style={[styles.flex1, styles.pageWrapper]}>
{this.props.iou.loading && <ActivityIndicator color={themeColors.text} />}
<View style={[
styles.flex1,
styles.flexRow,
styles.w100,
styles.alignItemsCenter,
styles.justifyContentCenter,
]}
>
<Text style={styles.iouAmountText}>
{this.props.selectedCurrency}
</Text>
{this.props.isSmallScreenWidth
? <Text style={styles.iouAmountText}>{this.props.amount}</Text>
: (
<View>
<TextInputFocusable
style={[styles.iouAmountTextInput,
{width: Math.max(5, this.state.textInputWidth)}]}
onKeyPress={(event) => {
this.props.numberPressed(event.key);
event.preventDefault();
}}
ref={el => this.textInput = el}
defaultValue={this.props.amount}
textAlign="left"
/>
<Text
style={[styles.iouAmountText, styles.invisible, {left: 100000}]}
onLayout={e => this.setState({textInputWidth: e.nativeEvent.layout.width})}
>
{this.props.amount}
</Text>
</View>
)}
</View>
<View style={[styles.w100, styles.justifyContentEnd]}>
{this.props.isSmallScreenWidth
? <BigNumberPad numberPressed={this.props.numberPressed} />
: <View />}
<TouchableOpacity
style={[styles.button, styles.w100, styles.mt5, styles.buttonSuccess,
this.props.isNextButtonDisabled ? styles.buttonSuccessDisabled : {}]}
onPress={this.props.onStepComplete}
disabled={this.props.isNextButtonDisabled}
>
<Text style={[styles.buttonText, styles.buttonSuccessText]}>
Next
</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
IOUAmountPage.displayName = 'IOUAmountPage';
IOUAmountPage.propTypes = propTypes;
IOUAmountPage.defaultProps = defaultProps;

export default withOnyx({
export default withWindowDimensions(withOnyx({
iou: {key: ONYXKEYS.IOU},
})(IOUAmountPage);
})(IOUAmountPage));
16 changes: 16 additions & 0 deletions src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ const styles = {
borderWidth: 0,
},

buttonSuccessDisabled: {
opacity: 0.5,
},

buttonSuccessHovered: {
backgroundColor: themeColors.buttonSuccessHoveredBG,
borderWidth: 0,
Expand Down Expand Up @@ -1092,6 +1096,18 @@ const styles = {
height: 24,
lineHeight: 20,
},

iouAmountText: {
fontFamily: fontFamily.GTA_BOLD,
fontWeight: fontWeightBold,
fontSize: variables.iouAmountTextSize,
},

iouAmountTextInput: addOutlineWidth({
fontFamily: fontFamily.GTA_BOLD,
fontWeight: fontWeightBold,
fontSize: variables.iouAmountTextSize,
}, 0),
};

const baseCodeTagStyles = {
Expand Down
1 change: 1 addition & 0 deletions src/styles/variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default {
iconSizeSmall: 16,
iconSizeNormal: 20,
iconSizeLarge: 24,
iouAmountTextSize: 40,
mobileResponsiveWidthBreakpoint: 800,
safeInsertPercentage: 0.7,
sideBarWidth: 375,
Expand Down

0 comments on commit fa6185b

Please sign in to comment.