Skip to content

Commit

Permalink
JSAEM2-29 feat: provide custom keyboard with calculation (#15)
Browse files Browse the repository at this point in the history
* JSAEM2-29 feat: provide custom keyboard with calculation

* JSAEM2-29 fixed: basic request changes from QA

* JSAEM 2-29 fixed: applied all request changes

* JSAEM2-29 fixing: capital issue

* JSAEM2-29 fixed: capital issue

* JSAEM 2-29 fixed: naming issues
  • Loading branch information
MichaelCTH authored and ikarasz committed Dec 22, 2019
1 parent b74bd2f commit c155679
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 40 deletions.
70 changes: 70 additions & 0 deletions src/TransCreator/Calculator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export const keyboardLayout = [
[1, 4, 7, 'C'],
[2, 5, 8, '0'],
[3, 6, 9, '.'],
['+', '-', 'Remove', 'Add'],
];

const isNumber = (value) => !Number.isNaN(parseFloat(value));

const getLastNum = (str) => {
let rst = '';
for (let iter = str.length - 1; iter >= 0; iter -= 1) {
if (['+', '-'].indexOf(str.charAt(iter)) >= 0) {
break;
}
rst = `${str.charAt(iter)}${rst}`;
}
return rst;
};

export const append = (oriStr, newChar) => {
const lastNum = getLastNum(oriStr);
if (newChar === '.') {
if (lastNum.indexOf('.') < 0) {
return `${oriStr}.`;
}
return oriStr;
}

if (isNumber(newChar) && lastNum.indexOf('.') >= 0 && lastNum.split('.')[1].length === 2) {
return oriStr;
}

return oriStr + newChar;
};

export const removeLast = (oriStr) => oriStr.slice(0, -1);

export const getResult = (oriStr) => {
const operationArr = [];
let temp = '';
for (let i = 0; i < oriStr.length; i += 1) {
if (['+', '-'].indexOf(oriStr.charAt(i)) >= 0) {
operationArr.push(temp);
operationArr.push(oriStr.charAt(i));
temp = '';
} else {
temp += oriStr.charAt(i);
}
}

if (temp !== '') {
operationArr.push(temp);
}

let result = 0;
let sign = 1;
operationArr.forEach((i) => {
if (i === '+') {
sign = 1;
} else if (i === '-') {
sign = -1;
} else {
temp = parseFloat(i);
result += sign * temp;
}
});

return result.toFixed(2).toString();
};
73 changes: 73 additions & 0 deletions src/TransCreator/CalculatorKeyboard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import {
append, removeLast, keyboardLayout, getResult,
} from './Calculator';
import styles from './Style';
import DateSelector from './DateSelector';
import KeyboardButton from './KeyboardButton';

export default function CalculatorKeyboard({
calculable,
expStr,
createHandler,
transDate,
setTransDate,
onExpressionChange,
}) {
const wrapResult = (exp) => ({
expression: exp,
result: getResult(exp),
});

const pressHandler = (pressedVal) => {
if (!calculable) {
return;
}

switch (pressedVal) {
case 'Add':
createHandler();
break;
case 'C':
onExpressionChange(wrapResult(''));
break;
case 'Remove':
onExpressionChange(wrapResult(removeLast(expStr)));
break;
default:
onExpressionChange(wrapResult(append(expStr, pressedVal)));
}
};

return (
<View style={styles.keyboardLayout}>
<DateSelector transDate={transDate} setTransDate={setTransDate} />
<View style={styles.keyboardRowLayout}>
{keyboardLayout.map((row, idx) => (
<View key={`row${idx + 1}`}>
{row.map((cell, idx2) => (
<View key={`cell${idx + 1}-${idx2 + 1}`}>
<KeyboardButton btnVal={cell.toString()} pressHandler={pressHandler} />
</View>
))}
</View>
))}
</View>
</View>
);
}

CalculatorKeyboard.propTypes = {
calculable: PropTypes.bool.isRequired,
expStr: PropTypes.string,
createHandler: PropTypes.func.isRequired,
transDate: PropTypes.number.isRequired,
setTransDate: PropTypes.func.isRequired,
onExpressionChange: PropTypes.func.isRequired,
};

CalculatorKeyboard.defaultProps = {
expStr: '',
};
7 changes: 3 additions & 4 deletions src/TransCreator/DateSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ import styles from './Style';
const DateSelector = ({ transDate, setTransDate }) => {
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
return (
<>
<View style={styles.dateSection}>
<MaterialIcons name="date-range" style={styles.dateItemIcon} />
<Text>Date: </Text>
<View style={styles.dateView}>
<Text
onPress={() => setDatePickerVisibility(true)}
style={{ padding: 5 }}
style={{ color: 'grey' }}
>
{moment.unix(transDate).format('MM/DD/YYYY')}
</Text>
Expand All @@ -31,7 +30,7 @@ const DateSelector = ({ transDate, setTransDate }) => {
titleStyle={{ color: '#5C6BC0' }}
/>
</View>
</>
</View>
);
};

Expand Down
25 changes: 25 additions & 0 deletions src/TransCreator/IconButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { Button } from 'react-native-elements';
import PropTypes from 'prop-types';
import btnColor from '../Common/Color';
import styles from './Style';

const IconButton = ({ iconName, onPress }) => (
<Button
type="outline"
icon={{
name: iconName,
size: 25,
color: btnColor.grey,
}}
buttonStyle={styles.keyboardBtn}
onPress={onPress}
/>
);

IconButton.propTypes = {
iconName: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
};

export default IconButton;
39 changes: 39 additions & 0 deletions src/TransCreator/KeyboardButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import { Button } from 'react-native-elements';
import PropTypes from 'prop-types';
import btnColor from '../Common/Color';
import IconButton from './IconButton';
import styles from './Style';

const NumberBtn = ({ btnVal, onPress }) => (
<Button
type="outline"
buttonStyle={styles.keyboardBtn}
title={btnVal.toString()}
titleStyle={{ color: btnColor.grey }}
onPress={onPress}
/>
);

const KeyboardButton = ({ btnVal, pressHandler }) => {
switch (btnVal) {
case 'Add':
return <IconButton iconName="playlist-add" onPress={() => pressHandler(btnVal)} />;
case 'Remove':
return <IconButton iconName="backspace" onPress={() => pressHandler(btnVal)} />;
default:
return <NumberBtn btnVal={btnVal} onPress={() => pressHandler(btnVal)} />;
}
};

NumberBtn.propTypes = {
btnVal: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
};

KeyboardButton.propTypes = {
btnVal: PropTypes.string.isRequired,
pressHandler: PropTypes.func.isRequired,
};

export default KeyboardButton;
26 changes: 11 additions & 15 deletions src/TransCreator/PageBanner.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React from 'react';
import {
View, Text, Platform,
View, Text,
} from 'react-native';
import { Input, Icon } from 'react-native-elements';
import { Icon } from 'react-native-elements';
import { LinearGradient } from 'expo-linear-gradient';

import PropTypes from 'prop-types';
import themeStyle from '../Common/themeStyle';
import themeColor from '../Common/Color';
import styles from './Style';
import numDecorator from './numericInputDecorator';

export default function PageBanner({
transLabel, transType, transAmount, setTransAmount,
transLabel, transType, transAmount, expStr,
}) {
let displayAmount = transAmount === '' ? '0.00' : transAmount;
displayAmount = transType === 'Expense' ? `-$${displayAmount}` : `$${displayAmount}`;
return (
transLabel.name
? (
Expand All @@ -27,15 +28,10 @@ export default function PageBanner({
<Icon name={transLabel.icon} type={transLabel.iconFamily} color="#ffffff" size={40} />
<Text style={[styles.headerText, { color: '#ffffff' }]}>{transLabel.name || 'undefined'}</Text>
</View>
<Input
placeholder="$ 0.00"
keyboardType={Platform.OS === 'ios' ? 'numeric' : 'decimal-pad'}
inputStyle={[styles.headerText, { textAlign: 'right', color: '#ffffff' }]}
value={transAmount ? transAmount.toString() : null}
containerStyle={{ width: 150 }}
inputContainerStyle={{ borderBottomWidth: 0 }}
onChangeText={(val) => setTransAmount(numDecorator(val))}
/>
<View style={styles.headerDigitSection}>
<Text style={styles.headerDigitResult}>{ displayAmount }</Text>
<Text style={styles.headerDigitExp}>{ expStr }</Text>
</View>
</View>
</LinearGradient>
)
Expand All @@ -56,7 +52,7 @@ PageBanner.propTypes = {
}),
transType: PropTypes.string,
transAmount: PropTypes.string,
setTransAmount: PropTypes.func,
expStr: PropTypes.string,
};

PageBanner.defaultProps = {
Expand All @@ -68,5 +64,5 @@ PageBanner.defaultProps = {
},
transType: '',
transAmount: '',
setTransAmount: null,
expStr: '',
};
60 changes: 53 additions & 7 deletions src/TransCreator/Style.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { StyleSheet } from 'react-native';
import { StyleSheet, Dimensions } from 'react-native';

const DeviceWidth = Dimensions.get('window').width;

export default StyleSheet.create({
footerIOS: {
Expand All @@ -8,20 +10,30 @@ export default StyleSheet.create({
footerAndroid: {
backgroundColor: 'transparent',
},
dateItem: {
borderBottomWidth: 0,
marginTop: 30,
marginBottom: 20,
dateSection: {
flex: 1,
flexDirection: 'row',
backgroundColor: 'white',
width: DeviceWidth,
borderTopWidth: 1,
borderColor: '#f0f0f0',
height: 35,
alignItems: 'center',
},
dateItemIcon: {
fontSize: 25,
marginRight: 15,
marginLeft: 15,
color: 'grey',
},
dateView: {
borderWidth: 1,
borderColor: '#5C6BC0',
borderRadius: 10,
marginLeft: 20,
marginLeft: 5,
paddingLeft: 7,
paddingRight: 7,
paddingTop: 2,
paddingBottom: 2,
},
confirmButton: {
marginLeft: 30,
Expand Down Expand Up @@ -84,4 +96,38 @@ export default StyleSheet.create({
fontSize: 20,
fontWeight: '400',
},
headerDigitSection: {
flex: 1,
flexDirection: 'column',
alignItems: 'flex-end',
},
headerDigitResult: {
color: '#ffffff',
fontSize: 20,
fontWeight: '400',
},
headerDigitExp: {
color: '#EEEEEE',
fontSize: 17,
fontWeight: '400',
},
keyboardBtn: {
height: DeviceWidth * 0.15,
width: DeviceWidth * 0.25,
borderRadius: 0,
borderColor: '#f0f0f0',
},
keyboardLayout: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
bottom: 0,
},
keyboardRowLayout: {
flexDirection: 'row',
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
},
});
Loading

0 comments on commit c155679

Please sign in to comment.