Merge pull request #1964 from ecency/nt/beneficiary-modal

Nt/beneficiary modal
This commit is contained in:
Feruz M 2021-06-07 12:45:13 +03:00 committed by GitHub
commit aaa8736498
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 332 additions and 187 deletions

View File

@ -86,7 +86,9 @@ export default EStyleSheet.create({
},
beneficiaryModal: {
flex: 1,
backgroundColor: '$modalBackground',
backgroundColor: '$primaryBackgroundColor',
margin: 0,
paddingTop: 32,
paddingBottom: 16,
},
});

View File

@ -1,173 +0,0 @@
import React, { useState, useCallback, useEffect } from 'react';
import { View, FlatList, Text } from 'react-native';
import { useIntl } from 'react-intl';
import AsyncStorage from '@react-native-community/async-storage';
import { isArray, remove } from 'lodash';
import { lookupAccounts } from '../../providers/hive/dhive';
import { FormInput, MainButton, Tag } from '..';
import styles from './beneficiaryModalStyles';
const BeneficiaryModal = ({ username, handleOnSaveBeneficiaries, isDraft }) => {
const intl = useIntl();
const [beneficiaries, setBeneficiaries] = useState([
{ account: username, weight: 10000, isValid: true },
]);
useEffect(() => {
if (!isDraft) {
readTempBeneficiaries();
}
}, []);
const readTempBeneficiaries = async () => {
const tempBeneficiariesString = await AsyncStorage.getItem('temp-beneficiaries');
const tempBeneficiaries = JSON.parse(tempBeneficiariesString);
if (isArray(tempBeneficiaries)) {
tempBeneficiaries.forEach((item) => {
item.isValid = true;
});
setBeneficiaries(tempBeneficiaries);
}
};
const _addAccount = () => {
setBeneficiaries([...beneficiaries, { account: '', weight: 0, isValid: false }]);
};
const _onWeightInputChange = (value, index) => {
let _value = (parseInt(value, 10) || 0) * 100;
const _diff = _value - beneficiaries[index].weight;
beneficiaries[0].weight = beneficiaries[0].weight - _diff;
beneficiaries[index].weight = _value;
setBeneficiaries([...beneficiaries]);
};
const _onUsernameInputChange = (value, index) => {
beneficiaries[index].account = value;
setBeneficiaries([...beneficiaries]);
lookupAccounts(value).then((res) => {
const isValid =
res.includes(value) &&
beneficiaries[index].weight !== 0 &&
beneficiaries[index].weight <= 10000;
beneficiaries[index].isValid = isValid;
setBeneficiaries([...beneficiaries]);
});
};
const _isValid = () => {
return beneficiaries.every((item) => item.isValid);
};
const _onBlur = (item, index) => {
if (item.weight === 0) {
const newBeneficiaries = [...beneficiaries];
remove(newBeneficiaries, (current) => {
return current.account === item.account;
});
setBeneficiaries(newBeneficiaries);
}
};
const renderInputs = ({ item, index }) => {
const _isCurrentUser = item.account === username;
return (
<View style={styles.inputWrapper}>
<View style={styles.weightInput}>
<FormInput
isValid={_isCurrentUser || (item.weight !== 0 && item.weight <= 10000)}
isEditable={!_isCurrentUser}
value={`${item.weight / 100}`}
inputStyle={styles.weightFormInput}
wrapperStyle={styles.weightFormInputWrapper}
onChange={(value) => _onWeightInputChange(value, index)}
onBlur={() => _onBlur(item, index)}
/>
</View>
<View style={styles.usernameInput}>
<FormInput
rightIconName="at"
iconType="MaterialCommunityIcons"
isValid={_isCurrentUser || item.isValid}
//isEditable={!_isCurrentUser}
onChange={(value) => _onUsernameInputChange(value, index)}
placeholder={intl.formatMessage({
id: 'beneficiary_modal.username',
})}
type="username"
isFirstImage
value={item.account}
inputStyle={styles.usernameInput}
wrapperStyle={styles.usernameFormInputWrapper}
/>
</View>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.bodyWrapper}>
<FlatList
data={beneficiaries}
renderItem={renderInputs}
ListHeaderComponent={() => (
<View style={styles.inputWrapper}>
<View style={[styles.weightInput, { alignItems: 'center' }]}>
<Text style={styles.text}>
{intl.formatMessage({
id: 'beneficiary_modal.percent',
})}
</Text>
</View>
<View style={[styles.usernameInput, { alignItems: 'center' }]}>
<Text style={styles.text}>
{intl.formatMessage({
id: 'beneficiary_modal.username',
})}
</Text>
</View>
</View>
)}
ListFooterComponent={() => (
<View style={{ alignItems: 'flex-end', marginTop: 20 }}>
<Tag
value={intl.formatMessage({
id: 'beneficiary_modal.addAccount',
})}
isFilter
disabled={!_isValid()}
isPin={_isValid()}
onPress={_addAccount}
/>
</View>
)}
/>
</View>
<View style={styles.footerWrapper}>
<MainButton
style={styles.saveButton}
isDisable={!_isValid()}
onPress={() => handleOnSaveBeneficiaries(beneficiaries)}
text={intl.formatMessage({
id: 'beneficiary_modal.save',
})}
/>
</View>
</View>
);
};
export default BeneficiaryModal;

View File

@ -0,0 +1,298 @@
import React, { useState, useEffect } from 'react';
import { View, FlatList, Text, ScrollView } from 'react-native';
import { useIntl } from 'react-intl';
import AsyncStorage from '@react-native-community/async-storage';
import { isArray, debounce } from 'lodash';
import { lookupAccounts } from '../../providers/hive/dhive';
import { FormInput, MainButton, Tag, TextButton } from '..';
import styles from './beneficiaryModalStyles';
import EStyleSheet from 'react-native-extended-stylesheet';
import IconButton from '../iconButton';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { iteratorStream } from '@esteemapp/dhive/lib/utils';
const BeneficiaryModal = ({ username, handleOnSaveBeneficiaries, isDraft }) => {
const intl = useIntl();
const [beneficiaries, setBeneficiaries] = useState([
{ account: username, weight: 10000},
]);
const [newUsername, setNewUsername] = useState('');
const [newWeight, setNewWeight] = useState(0);
const [isUsernameValid, setIsUsernameValid] = useState(false);
const [isWeightValid, setIsWeightValid] = useState(false);
const [newEditable, setNewEditable] = useState(false);
useEffect(() => {
if (!isDraft) {
readTempBeneficiaries();
}
}, []);
const readTempBeneficiaries = async () => {
const tempBeneficiariesString = await AsyncStorage.getItem('temp-beneficiaries');
const tempBeneficiaries = JSON.parse(tempBeneficiariesString);
if (isArray(tempBeneficiaries)) {
tempBeneficiaries.forEach((item) => {
item.isValid = true;
});
setBeneficiaries(tempBeneficiaries);
}
};
const _onSavePress = () => {
if(newEditable){
beneficiaries.push({
account:newUsername,
weight:newWeight
})
}
handleOnSaveBeneficiaries(beneficiaries);
}
const _addAccount = () => {
if(isUsernameValid && isWeightValid){
beneficiaries.push({
account:newUsername,
weight:newWeight,
})
setBeneficiaries([...beneficiaries]);
}
setIsUsernameValid(false);
setIsWeightValid(false);
setNewWeight(0);
setNewUsername('');
setNewEditable(true);
};
const _onWeightInputChange = (value) => {
let _value = (parseInt(value, 10) || 0) * 100;
const _diff = _value - newWeight;
beneficiaries[0].weight = beneficiaries[0].weight - _diff;
setNewWeight(_value)
setIsWeightValid(_value > 0 && _value <= 10000)
setBeneficiaries([...beneficiaries]);
};
const _lookupAccounts = debounce((username) => {
lookupAccounts(username).then((res) => {
const isValid = res.includes(username)
//check if username duplicates else lookup contacts, done here to avoid debounce and post call mismatch
const notExistAlready = !beneficiaries.find((item)=>item.account === username);
setIsUsernameValid(isValid && notExistAlready)
});
}, 1000)
const _onUsernameInputChange = (value) => {
setNewUsername(value);
_lookupAccounts(value);
};
const _isValid = () => {
return !newEditable || (isUsernameValid && isWeightValid);
};
const _renderHeader = () => (
<View style={styles.inputWrapper}>
<View style={[styles.weightInput, { alignItems: 'center' }]}>
<Text style={styles.text}>
{intl.formatMessage({
id: 'beneficiary_modal.percent',
})}
</Text>
</View>
<View style={[styles.usernameInput, { alignItems: 'center' }]}>
<Text style={styles.text}>
{intl.formatMessage({
id: 'beneficiary_modal.username',
})}
</Text>
</View>
</View>
)
const _renderInput = () => {
const _onCancelPress = () => {
if(newWeight){
beneficiaries[0].weight = beneficiaries[0].weight + newWeight;
setBeneficiaries([...beneficiaries])
setNewWeight(0);
}
setNewEditable(false);
setIsWeightValid(false);
setIsUsernameValid(false);
setNewUsername('');
}
return (
<View style={styles.inputWrapper}>
<View style={styles.weightInput}>
<FormInput
isValid={isWeightValid}
value={`${newWeight / 100}`}
inputStyle={styles.weightFormInput}
wrapperStyle={styles.weightFormInputWrapper}
onChange={(value) => _onWeightInputChange(value)}
onBlur={() => {}}//_onBlur(item)}
keyboardType='numeric'
/>
</View>
<View style={styles.usernameInput}>
<FormInput
rightIconName="at"
iconType="MaterialCommunityIcons"
isValid={isUsernameValid}
onChange={(value) => _onUsernameInputChange(value)}
placeholder={intl.formatMessage({
id: 'beneficiary_modal.username',
})}
type="username"
isFirstImage
value={newUsername}
inputStyle={styles.usernameInput}
wrapperStyle={styles.usernameFormInputWrapper}
/>
</View>
<IconButton
name="close"
iconType="MaterialCommunityIcons"
color={EStyleSheet.value('$primaryBlack')}
size={24}
iconStyle={{paddingLeft:8}}
onPress={_onCancelPress}
/>
</View>
);
};
const _renderFooter = () => (
<>
{newEditable && _renderInput()}
<View style={{marginTop: 20, marginBottom:32 }}>
<TextButton
text={intl.formatMessage({
id: 'beneficiary_modal.addAccount',
})}
disabled={!isAllValid}
onPress={_addAccount}
textStyle={{
color:EStyleSheet.value(isAllValid?'$primaryBlue':"$iconColor"),
fontWeight:'bold'
}}
/>
</View>
</>
)
const _renderItem = ({ item, index }) => {
const _isCurrentUser = item.account === username;
const _onRemovePress = () => {
beneficiaries[0].weight = beneficiaries[0].weight + item.weight;
beneficiaries.splice(index, 1);
setBeneficiaries([...beneficiaries]);
}
return (
<View style={styles.inputWrapper}>
<View style={styles.weightInput}>
<FormInput
isValid={true}
isEditable={false}
value={`${item.weight / 100}`}
inputStyle={styles.weightFormInput}
wrapperStyle={styles.weightFormInputWrapper}
/>
</View>
<View style={styles.usernameInput}>
<FormInput
isValid={true}
isEditable={false}
type="username"
isFirstImage
value={item.account}
inputStyle={styles.usernameInput}
wrapperStyle={styles.usernameFormInputWrapper}
/>
</View>
{!_isCurrentUser ? (
<IconButton
name="close"
iconType="MaterialCommunityIcons"
size={24}
color={EStyleSheet.value('$primaryBlack')}
iconStyle={{paddingLeft:8}}
onPress={_onRemovePress}
/>
):(
<View style={{width:30}} />
)}
</View>
);
};
const isAllValid = _isValid();
return (
<View style={styles.container}>
<KeyboardAwareScrollView style={styles.bodyWrapper}>
<FlatList
data={beneficiaries}
renderItem={_renderItem}
ListHeaderComponent={_renderHeader}
showsVerticalScrollIndicator={false}
/>
{_renderFooter()}
</KeyboardAwareScrollView>
<View style={styles.footerWrapper}>
<MainButton
style={styles.saveButton}
isDisable={!_isValid()}
onPress={_onSavePress}
text={intl.formatMessage({
id: 'beneficiary_modal.save',
})}
/>
</View>
</View>
);
};
export default BeneficiaryModal;

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { View, Platform } from 'react-native';
import { View, Platform, TextInputProps, ViewStyle, TextStyle } from 'react-native';
import FastImage from 'react-native-fast-image';
// Components
@ -13,6 +13,21 @@ import { getResizedAvatar } from '../../../utils/image';
// Styles
import styles from './formInputStyles';
interface Props extends TextInputProps {
type:string;
isFirstImage:boolean;
isEditable?:boolean;
leftIconName?:string;
rightIconName?:string;
iconType?:string;
wrapperStyle:ViewStyle;
height:number;
inputStyle:TextStyle;
isValid:boolean;
onChange?:(value:string)=>void;
}
const FormInputView = ({
placeholder,
type,
@ -29,7 +44,8 @@ const FormInputView = ({
isValid,
value,
onBlur,
}) => {
...props
}:Props) => {
const [_value, setValue] = useState(value || '');
const [inputBorderColor, setInputBorderColor] = useState('#e7e7e7');
const [_isValid, setIsValid] = useState(true);
@ -118,6 +134,7 @@ const FormInputView = ({
placeholderTextColor={isDarkTheme ? '#526d91' : '#788187'}
autoCorrect={false}
contextMenuHidden={false}
{...props}
/>
)}
</ThemeContainer>