improved beneficiary modal

1. manage validity state separate
2. 500 ms debounce for account check
3. tweak UI for better presentation
This commit is contained in:
Nouman Tahir 2021-05-29 16:57:33 +05:00
parent fa77aa48a2
commit 18016e604d
3 changed files with 65 additions and 41 deletions

View File

@ -1,21 +1,26 @@
import React, { useState, useCallback, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { View, FlatList, Text } from 'react-native'; import { View, FlatList, Text } from 'react-native';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
import { isArray, remove } from 'lodash'; import { isArray, debounce } from 'lodash';
import { lookupAccounts } from '../../providers/hive/dhive'; import { lookupAccounts } from '../../providers/hive/dhive';
import { FormInput, MainButton, Tag } from '..'; import { FormInput, MainButton, Tag, TextButton } from '..';
import styles from './beneficiaryModalStyles'; import styles from './beneficiaryModalStyles';
import EStyleSheet from 'react-native-extended-stylesheet';
const BeneficiaryModal = ({ username, handleOnSaveBeneficiaries, isDraft }) => { const BeneficiaryModal = ({ username, handleOnSaveBeneficiaries, isDraft }) => {
const intl = useIntl(); const intl = useIntl();
const [beneficiaries, setBeneficiaries] = useState([ const [beneficiaries, setBeneficiaries] = useState([
{ account: username, weight: 10000, isValid: true }, { account: username, weight: 10000},
]); ]);
const [validity, setValidity] = useState([{
weightValid:true,
usernameValid:true
}])
useEffect(() => { useEffect(() => {
if (!isDraft) { if (!isDraft) {
@ -37,7 +42,8 @@ const BeneficiaryModal = ({ username, handleOnSaveBeneficiaries, isDraft }) => {
}; };
const _addAccount = () => { const _addAccount = () => {
setBeneficiaries([...beneficiaries, { account: '', weight: 0, isValid: false }]); setBeneficiaries([...beneficiaries, { account: '', weight: 0}]);
setValidity([...validity, {weightValid:false, usernameValid:false}])
}; };
const _onWeightInputChange = (value, index) => { const _onWeightInputChange = (value, index) => {
@ -47,60 +53,71 @@ const BeneficiaryModal = ({ username, handleOnSaveBeneficiaries, isDraft }) => {
beneficiaries[0].weight = beneficiaries[0].weight - _diff; beneficiaries[0].weight = beneficiaries[0].weight - _diff;
beneficiaries[index].weight = _value; beneficiaries[index].weight = _value;
setBeneficiaries([...beneficiaries]); validity[index].weightValid = _value > 0 && _value <= 10000;
setBeneficiaries(beneficiaries);
setValidity([...validity])
}; };
const _onUsernameInputChange = (value, index) => { const _lookupAccounts = debounce((username, index)=>{
beneficiaries[index].account = value; lookupAccounts(username).then((res) => {
setBeneficiaries([...beneficiaries]);
lookupAccounts(value).then((res) => {
const isValid = const isValid =
res.includes(value) && res.includes(username)
beneficiaries[index].weight !== 0 && validity[index].usernameValid = isValid;
beneficiaries[index].weight <= 10000; setValidity([...validity]);
beneficiaries[index].isValid = isValid;
setBeneficiaries([...beneficiaries]);
}); });
}, 500)
const _onUsernameInputChange = (value, index) => {
_lookupAccounts(value, index);
beneficiaries[index].account = value;
setBeneficiaries(beneficiaries);
}; };
const _isValid = () => { const _isValid = () => {
return beneficiaries.every((item) => item.isValid); return validity.every((item) => (item.usernameValid && item.weightValid));
}; };
const _onBlur = (item, index) => { const _onBlur = (item) => {
if (item.weight === 0) { if (item.weight === 0) {
const newBeneficiaries = [...beneficiaries]; const newBeneficiaries = [...beneficiaries];
remove(newBeneficiaries, (current) => { const newValidity = [...validity];
return current.account === item.account;
}); const index = newBeneficiaries.findIndex((current)=>current.account === item.account);
if(index >= 0){
newBeneficiaries.splice(index, 1);
newValidity.splice(index, 1);
}
setBeneficiaries(newBeneficiaries); setBeneficiaries(newBeneficiaries);
setValidity(newValidity);
} }
}; };
const renderInputs = ({ item, index }) => {
const renderInput = ({ item, index }) => {
const _isCurrentUser = item.account === username; const _isCurrentUser = item.account === username;
const {weightValid, usernameValid} = validity[index];
return ( return (
<View style={styles.inputWrapper}> <View style={styles.inputWrapper}>
<View style={styles.weightInput}> <View style={styles.weightInput}>
<FormInput <FormInput
isValid={_isCurrentUser || (item.weight !== 0 && item.weight <= 10000)} isValid={_isCurrentUser || weightValid}
isEditable={!_isCurrentUser} isEditable={!_isCurrentUser}
value={`${item.weight / 100}`} value={`${item.weight / 100}`}
inputStyle={styles.weightFormInput} inputStyle={styles.weightFormInput}
wrapperStyle={styles.weightFormInputWrapper} wrapperStyle={styles.weightFormInputWrapper}
onChange={(value) => _onWeightInputChange(value, index)} onChange={(value) => _onWeightInputChange(value, index)}
onBlur={() => _onBlur(item, index)} onBlur={() => _onBlur(item)}
/> />
</View> </View>
<View style={styles.usernameInput}> <View style={styles.usernameInput}>
<FormInput <FormInput
rightIconName="at" rightIconName="at"
iconType="MaterialCommunityIcons" iconType="MaterialCommunityIcons"
isValid={_isCurrentUser || item.isValid} isValid={_isCurrentUser || usernameValid}
//isEditable={!_isCurrentUser} //isEditable={!_isCurrentUser}
onChange={(value) => _onUsernameInputChange(value, index)} onChange={(value) => _onUsernameInputChange(value, index)}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
@ -117,12 +134,16 @@ const BeneficiaryModal = ({ username, handleOnSaveBeneficiaries, isDraft }) => {
); );
}; };
const isAllValid = _isValid();
return ( return (
<View style={styles.container}> <View style={styles.container}>
<View style={styles.bodyWrapper}> <View style={styles.bodyWrapper}>
<FlatList <FlatList
data={beneficiaries} data={beneficiaries}
renderItem={renderInputs} renderItem={renderInput}
extraData={validity}
ListHeaderComponent={() => ( ListHeaderComponent={() => (
<View style={styles.inputWrapper}> <View style={styles.inputWrapper}>
<View style={[styles.weightInput, { alignItems: 'center' }]}> <View style={[styles.weightInput, { alignItems: 'center' }]}>
@ -142,15 +163,17 @@ const BeneficiaryModal = ({ username, handleOnSaveBeneficiaries, isDraft }) => {
</View> </View>
)} )}
ListFooterComponent={() => ( ListFooterComponent={() => (
<View style={{ alignItems: 'flex-end', marginTop: 20 }}> <View style={{marginTop: 20 }}>
<Tag <TextButton
value={intl.formatMessage({ text={intl.formatMessage({
id: 'beneficiary_modal.addAccount', id: 'beneficiary_modal.addAccount',
})} })}
isFilter disabled={!isAllValid}
disabled={!_isValid()}
isPin={_isValid()}
onPress={_addAccount} onPress={_addAccount}
textStyle={{
color:EStyleSheet.value(isAllValid?'$primaryBlue':"$iconColor"),
fontWeight:'bold'
}}
/> />
</View> </View>
)} )}

View File

@ -5,20 +5,21 @@ export default EStyleSheet.create({
flex: 1, flex: 1,
justifyContent: 'space-between', justifyContent: 'space-between',
padding: 16, padding: 16,
backgroundColor:'$modalBackground',
}, },
bodyWrapper: { flex: 3, paddingTop: 20 }, bodyWrapper: { flex: 3, paddingTop: 20 },
inputWrapper: { flexDirection: 'row', alignItems: 'center' }, inputWrapper: { flexDirection: 'row', alignItems: 'center' },
text: { color: '$primaryBlack', marginBottom: 8 }, text: { color: '$primaryBlack', marginBottom: 8 },
weightInput: { flex: 1 }, weightInput: { flex: 1 },
weightFormInput: { textAlign: 'center', color: '$primaryBlack' }, weightFormInput: { textAlign: 'center', color: '$primaryBlack' },
weightFormInputWrapper: { marginTop: 0 }, weightFormInputWrapper: { marginTop: 8, borderRadius:12 },
usernameInput: { flex: 3, color: '$primaryBlack', marginLeft: 16 }, usernameInput: { flex: 3, color: '$primaryBlack', marginLeft: 16 },
usernameFormInputWrapper: { marginTop: 0, marginLeft: 10 }, usernameFormInputWrapper: { marginTop: 8, marginLeft: 10, borderRadius:12 },
footerWrapper: { flex: 1 }, footerWrapper: { paddingTop:16, paddingBottom:16 },
saveButton: { saveButton: {
width: 100, width: 140,
height: 44, height: 44,
alignSelf: 'center', alignSelf: 'flex-end',
justifyContent: 'center', justifyContent: 'center',
}, },
}); });

View File

@ -3,9 +3,9 @@ import { Text, View, TouchableWithoutFeedback } from 'react-native';
import styles from './textButtonStyles'; import styles from './textButtonStyles';
const TextButtonView = ({ text, onPress, style, textStyle }) => ( const TextButtonView = ({ text, onPress, style, textStyle, disabled }) => (
<Fragment> <Fragment>
<TouchableWithoutFeedback style={[styles.button]} onPress={() => onPress && onPress()}> <TouchableWithoutFeedback style={[styles.button]} disabled={disabled} onPress={() => onPress && onPress()}>
<View style={style}> <View style={style}>
<Text style={[styles.buttonText, textStyle]}>{text}</Text> <Text style={[styles.buttonText, textStyle]}>{text}</Text>
</View> </View>