Merge pull request #1146 from esteemapp/feature/profile-edit

Feature/profile edit
This commit is contained in:
uğur erdal 2019-09-15 12:19:24 +03:00 committed by GitHub
commit 43aacb970e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 831 additions and 145 deletions

View File

@ -0,0 +1,46 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
headerContainer: {
height: 100,
flexDirection: 'row',
padding: 21,
},
backIcon: {
color: '$white',
},
wrapper: {
marginLeft: 16,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
textWrapper: {
marginLeft: 16,
},
name: {
color: '$white',
fontSize: 14,
fontWeight: 'bold',
},
username: {
color: '$white',
fontSize: 12,
marginTop: 4,
},
addIcon: {
color: '$white',
textAlign: 'center',
},
addButton: {
backgroundColor: '$iconColor',
width: 20,
height: 20,
borderRadius: 20 / 2,
borderColor: '$white',
borderWidth: 1,
position: 'absolute',
bottom: 0,
left: 45,
},
});

View File

@ -0,0 +1,59 @@
import React from 'react';
import { withNavigation } from 'react-navigation';
import { View, Text } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { UserAvatar } from '../userAvatar';
import { IconButton } from '../iconButton';
// Styles
import styles from './avatarHeaderStyles';
const AvatarHeader = ({
username,
name,
reputation,
navigation,
avatarUrl,
showImageUploadActions,
}) => (
<LinearGradient
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
colors={['#357ce6', '#2d5aa0']}
style={styles.headerView}
>
<View style={styles.headerContainer}>
<IconButton
iconStyle={styles.backIcon}
iconType="MaterialIcons"
name="arrow-back"
onPress={navigation.goBack}
size={25}
/>
<View style={styles.wrapper}>
<UserAvatar
key={avatarUrl || username}
noAction
size="xl"
username={username}
avatarUrl={avatarUrl}
/>
<IconButton
iconStyle={styles.addIcon}
style={styles.addButton}
iconType="MaterialCommunityIcons"
name="plus"
onPress={showImageUploadActions}
size={15}
/>
<View style={styles.textWrapper}>
{!!name && <Text style={styles.name}>{name}</Text>}
<Text style={styles.username}>{`@${username} (${reputation})`}</Text>
</View>
</View>
</View>
</LinearGradient>
);
export default withNavigation(AvatarHeader);

View File

@ -0,0 +1,3 @@
import AvatarHeader from './avatarHeaderView';
export { AvatarHeader };

View File

@ -100,7 +100,7 @@ class BasicHeaderView extends Component {
<IconButton <IconButton
iconStyle={[styles.backIcon, isModalHeader && styles.closeIcon]} iconStyle={[styles.backIcon, isModalHeader && styles.closeIcon]}
iconType="MaterialIcons" iconType="MaterialIcons"
name={isModalHeader ? 'arrow-back' : 'arrow-back'} name="arrow-back"
onPress={() => (isModalHeader ? handleOnPressClose() : handleOnPressBackButton())} onPress={() => (isModalHeader ? handleOnPressClose() : handleOnPressBackButton())}
disabled={disabled} disabled={disabled}
/> />

View File

@ -4,8 +4,7 @@ export default EStyleSheet.create({
wrapper: { wrapper: {
borderTopLeftRadius: 8, borderTopLeftRadius: 8,
borderTopRightRadius: 8, borderTopRightRadius: 8,
marginHorizontal: 30, marginTop: 16,
marginVertical: 10,
flexDirection: 'row', flexDirection: 'row',
backgroundColor: '$primaryGray', backgroundColor: '$primaryGray',
height: 60, height: 60,
@ -21,7 +20,6 @@ export default EStyleSheet.create({
textInput: { textInput: {
flex: 0.7, flex: 0.7,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center',
}, },
icon: { icon: {
flex: 0.15, flex: 0.15,

View File

@ -1,12 +1,11 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import { Icon } from '../../icon';
// Constants
// Components // Components
import { TextInput } from '../../textInput'; import { TextInput } from '../../textInput';
import { Icon } from '../../icon';
// Styles // Styles
import styles from './formInputStyles'; import styles from './formInputStyles';
@ -19,28 +18,18 @@ class FormInputView extends Component {
* @prop { boolean } isEditable - Can permission edit. * @prop { boolean } isEditable - Can permission edit.
* @prop { boolean } isValid - This delegate input valit or not. * @prop { boolean } isValid - This delegate input valit or not.
* @prop { boolean } secureTextEntry - For hiding password value. * @prop { boolean } secureTextEntry - For hiding password value.
*
*
*
*/ */
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
value: '', value: props.value || '',
inputBorderColor: '#c1c5c7', inputBorderColor: '#e7e7e7',
isValid: true, isValid: true,
formInputWidth: '99%',
}; };
} }
// Component Life Cycles // Component Life Cycles
componentWillMount() {
setTimeout(() => {
this.setState({ formInputWidth: '100%' });
}, 100);
}
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const { isValid } = this.props; const { isValid } = this.props;
@ -54,18 +43,15 @@ class FormInputView extends Component {
const { onChange } = this.props; const { onChange } = this.props;
this.setState({ value }); this.setState({ value });
onChange && onChange(value); if (onChange) onChange(value);
}; };
_handleOnFocus = () => { _handleOnFocus = () => {
const { inputBorderColor } = this.state;
if (inputBorderColor !== '#357ce6') {
this.setState({ inputBorderColor: '#357ce6' }); this.setState({ inputBorderColor: '#357ce6' });
}
}; };
render() { render() {
const { inputBorderColor, isValid, value, formInputWidth } = this.state; const { inputBorderColor, isValid, value } = this.state;
const { const {
placeholder, placeholder,
type, type,
@ -75,6 +61,9 @@ class FormInputView extends Component {
rightIconName, rightIconName,
secureTextEntry, secureTextEntry,
iconType, iconType,
wrapperStyle,
height,
inputStyle,
} = this.props; } = this.props;
return ( return (
<View <View
@ -83,6 +72,7 @@ class FormInputView extends Component {
{ {
borderBottomColor: isValid ? inputBorderColor : 'red', borderBottomColor: isValid ? inputBorderColor : 'red',
}, },
wrapperStyle,
]} ]}
> >
{isFirstImage && value && value.length > 2 ? ( {isFirstImage && value && value.length > 2 ? (
@ -97,19 +87,23 @@ class FormInputView extends Component {
/> />
</View> </View>
) : ( ) : (
rightIconName && (
<Icon iconType={iconType || 'MaterialIcons'} name={rightIconName} style={styles.icon} /> <Icon iconType={iconType || 'MaterialIcons'} name={rightIconName} style={styles.icon} />
)
)} )}
<View style={styles.textInput}> <View style={styles.textInput}>
<TextInput <TextInput
onFocus={() => this._handleOnFocus()} style={inputStyle}
onFocus={() => this.setState({ inputBorderColor: '#357ce6' })}
onBlur={() => this.setState({ inputBorderColor: '#e7e7e7' })}
autoCapitalize="none" autoCapitalize="none"
secureTextEntry={secureTextEntry} secureTextEntry={secureTextEntry}
height={height}
placeholder={placeholder} placeholder={placeholder}
editable={isEditable || true} editable={isEditable || true}
textContentType={type} textContentType={type}
onChangeText={val => this._handleOnChange(val)} onChangeText={this._handleOnChange}
value={value} value={value}
style={{ width: formInputWidth }}
/> />
</View> </View>

View File

@ -1,12 +1,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { withNavigation } from 'react-navigation'; import { withNavigation } from 'react-navigation';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { get, has } from 'lodash';
// Services and Actions
// Middleware
// Constants
// Component // Component
import HeaderView from '../view/headerView'; import HeaderView from '../view/headerView';
@ -30,7 +25,7 @@ class HeaderContainer extends PureComponent {
_handleOpenDrawer = () => { _handleOpenDrawer = () => {
const { navigation } = this.props; const { navigation } = this.props;
if (navigation && navigation.openDrawer && typeof navigation.openDrawer === 'function') { if (has(navigation, 'openDrawer') && typeof get(navigation, 'openDrawer') === 'function') {
navigation.openDrawer(); navigation.openDrawer();
} }
}; };
@ -52,19 +47,11 @@ class HeaderContainer extends PureComponent {
isLoginDone, isLoginDone,
isDarkTheme, isDarkTheme,
} = this.props; } = this.props;
let displayName; const _user = isReverse && selectedUser ? selectedUser : currentAccount;
let username;
let reputation;
if (isReverse && selectedUser) { const displayName = get(_user, 'display_name');
displayName = selectedUser.display_name; const username = get(_user, 'name');
username = selectedUser.name; const reputation = get(_user, 'reputation');
reputation = selectedUser.reputation;
} else if (!isReverse) {
displayName = currentAccount.display_name;
username = currentAccount.name;
reputation = currentAccount.reputation;
}
return ( return (
<HeaderView <HeaderView

View File

@ -34,19 +34,20 @@ class HeaderView extends Component {
render() { render() {
const { const {
avatarUrl,
displayName, displayName,
handleOnPressBackButton, handleOnPressBackButton,
handleOpenDrawer, handleOpenDrawer,
intl, intl,
isDarkTheme,
isLoggedIn, isLoggedIn,
isLoginDone, isLoginDone,
isReverse, isReverse,
reputation, reputation,
username, username,
isDarkTheme,
} = this.props; } = this.props;
const { isSearchModalOpen } = this.state; const { isSearchModalOpen } = this.state;
let gredientColor = isDarkTheme ? ['#081c36', '#43638e'] : ['#2d5aa0', '#357ce6']; let gredientColor;
if (isReverse) { if (isReverse) {
gredientColor = isDarkTheme ? ['#43638e', '#081c36'] : ['#357ce6', '#2d5aa0']; gredientColor = isDarkTheme ? ['#43638e', '#081c36'] : ['#357ce6', '#2d5aa0'];

View File

@ -1,5 +1,5 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { TouchableHighlight } from 'react-native'; import { TouchableHighlight, ActivityIndicator } from 'react-native';
import { Icon } from '../../icon'; import { Icon } from '../../icon';
import styles from './iconButtonStyles'; import styles from './iconButtonStyles';
@ -22,14 +22,16 @@ const IconButton = ({
onPress, onPress,
size, size,
style, style,
isLoading,
}) => ( }) => (
<Fragment> <Fragment>
<TouchableHighlight <TouchableHighlight
style={[styles.iconButton, style]} style={[styles.iconButton, style]}
onPress={() => onPress && onPress()} onPress={() => !isLoading && onPress && onPress()}
underlayColor={backgroundColor || 'white'} underlayColor={backgroundColor || 'white'}
disabled={disabled} disabled={disabled}
> >
{!isLoading ? (
<Icon <Icon
style={[ style={[
color && { color }, color && { color },
@ -44,6 +46,9 @@ const IconButton = ({
iconType={iconType} iconType={iconType}
badgeCount={badgeCount} badgeCount={badgeCount}
/> />
) : (
<ActivityIndicator color="white" style={styles.activityIndicator} />
)}
</TouchableHighlight> </TouchableHighlight>
</Fragment> </Fragment>
); );

View File

@ -3,15 +3,16 @@ import { FormInput } from './formInput';
import { NumericKeyboard } from './numericKeyboard'; import { NumericKeyboard } from './numericKeyboard';
import { PinAnimatedInput } from './pinAnimatedInput'; import { PinAnimatedInput } from './pinAnimatedInput';
import { SideMenu } from './sideMenu'; import { SideMenu } from './sideMenu';
import { TextInput } from './textInput';
import Icon from './icon'; import Icon from './icon';
import Logo from './logo/logo'; import Logo from './logo/logo';
import Modal from './modal'; import Modal from './modal';
import { TextInput } from './textInput'; import PostBoost from './postBoost/postBoostView';
import PostButton from './postButton/postButtonView';
import ProfileEditForm from './profileEditForm/profileEditFormView';
import Promote from './promote/promoteView';
import ScaleSlider from './scaleSlider/scaleSliderView'; import ScaleSlider from './scaleSlider/scaleSliderView';
import UserListItem from './basicUIElements/view/userListItem/userListItem'; import UserListItem from './basicUIElements/view/userListItem/userListItem';
import PostButton from './postButton/postButtonView';
import Promote from './promote/promoteView';
import PostBoost from './postBoost/postBoostView';
export { export {
CircularButton, CircularButton,
@ -22,12 +23,13 @@ export {
Modal, Modal,
NumericKeyboard, NumericKeyboard,
PinAnimatedInput, PinAnimatedInput,
PostButton,
ProfileEditForm,
ScaleSlider, ScaleSlider,
SideMenu, SideMenu,
TextButton, TextButton,
TextInput, TextInput,
UserListItem, UserListItem,
PostButton,
Promote, Promote,
PostBoost, PostBoost,
}; };

View File

@ -0,0 +1,71 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
paddingHorizontal: 32,
paddingVertical: 16,
backgroundColor: '$primaryBackgroundColor',
flex: 1,
},
formStyle: {
backgroundColor: '$white',
height: 30,
marginTop: 8,
},
label: {
fontSize: 14,
color: '$primaryDarkText',
fontWeight: '500',
},
formItem: {
marginBottom: 24,
},
coverImg: {
borderRadius: 5,
height: 60,
marginBottom: 12,
alignSelf: 'stretch',
backgroundColor: '#296CC0',
},
coverImageWrapper: {},
addIcon: {
color: '$white',
textAlign: 'center',
},
addButton: {
backgroundColor: '$iconColor',
width: 20,
height: 20,
borderRadius: 20 / 2,
borderColor: '$white',
borderWidth: 1,
position: 'absolute',
bottom: 0,
right: 10,
},
saveButton: {
backgroundColor: '$primaryBlue',
width: 55,
height: 55,
borderRadius: 55 / 2,
position: 'absolute',
top: -25,
right: 10,
zIndex: 999,
borderWidth: 2,
borderColor: '$white',
},
saveIcon: {
color: '$white',
textAlign: 'center',
},
input: {
fontSize: 14,
color: '$primaryDarkText',
alignSelf: 'flex-start',
width: '100%',
height: 30,
},
});

View File

@ -0,0 +1,88 @@
import React from 'react';
import { withNavigation } from 'react-navigation';
import { View, TouchableOpacity, Image, Text, Platform } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { injectIntl } from 'react-intl';
// Images
import LIGHT_COVER_IMAGE from '../../assets/default_cover_image.png';
import DARK_COVER_IMAGE from '../../assets/dark_cover_image.png';
// Components
import { FormInput } from '../formInput';
import { IconButton } from '../iconButton';
// Utils
import { getResizedImage } from '../../utils/image';
// Styles
import styles from './profileEditFormStyles';
const ProfileEditFormView = ({
avatarUrl,
coverUrl,
formData,
handleOnItemChange,
handleOnSubmit,
intl,
isDarkTheme,
isLoading,
showImageUploadActions,
...props
}) => (
<View style={styles.container}>
<IconButton
iconStyle={styles.saveIcon}
style={styles.saveButton}
iconType="MaterialIcons"
name="save"
onPress={handleOnSubmit}
size={30}
isLoading={isLoading}
/>
<KeyboardAwareScrollView
enableAutoAutomaticScroll={Platform.OS === 'ios'}
contentContainerStyle={{ flexGrow: 1 }}
>
<TouchableOpacity style={styles.coverImgWrapper} onPress={showImageUploadActions}>
<Image
style={styles.coverImg}
source={{ uri: getResizedImage(coverUrl, 400) }}
defaultSource={isDarkTheme ? DARK_COVER_IMAGE : LIGHT_COVER_IMAGE}
/>
<IconButton
iconStyle={styles.addIcon}
style={styles.addButton}
iconType="MaterialCommunityIcons"
name="plus"
onPress={showImageUploadActions}
size={15}
/>
</TouchableOpacity>
{formData.map(item => (
<View style={styles.formItem} key={item.valueKey}>
<Text style={styles.label}>
{intl.formatMessage({
id: `profile.edit.${item.label}`,
})}
</Text>
<FormInput
wrapperStyle={styles.formStyle}
isValid
height={30}
onChange={value => handleOnItemChange(value, item.valueKey)}
placeholder={item.placeholder}
isEditable
type="none"
value={props[item.valueKey]}
inputStyle={styles.input}
/>
</View>
))}
</KeyboardAwareScrollView>
</View>
);
export default injectIntl(withNavigation(ProfileEditFormView));

View File

@ -21,6 +21,7 @@ import { DropdownButton } from '../../dropdownButton';
// Utils // Utils
import { makeCountFriendly } from '../../../utils/formatter'; import { makeCountFriendly } from '../../../utils/formatter';
import { getResizedImage } from '../../../utils/image';
// Styles // Styles
import styles from './profileSummaryStyles'; import styles from './profileSummaryStyles';
@ -67,6 +68,7 @@ class ProfileSummaryView extends PureComponent {
handleFollowUnfollowUser, handleFollowUnfollowUser,
handleOnFavoritePress, handleOnFavoritePress,
handleOnFollowsPress, handleOnFollowsPress,
handleOnPressProfileEdit,
handleUIChange, handleUIChange,
hoursRC, hoursRC,
hoursVP, hoursVP,
@ -94,7 +96,7 @@ class ProfileSummaryView extends PureComponent {
const isColumn = rowLength && DEVICE_WIDTH / rowLength <= 7.3; const isColumn = rowLength && DEVICE_WIDTH / rowLength <= 7.3;
const followButtonIcon = !isFollowing ? 'account-plus' : 'account-minus'; const followButtonIcon = !isFollowing ? 'account-plus' : 'account-minus';
const coverImageUrl = `https://steemitimages.com/400x0/${coverImage}`; const coverImageUrl = getResizedImage(coverImage, 400);
dropdownOpions.push(!isMuted ? 'MUTE' : 'UNMUTE'); dropdownOpions.push(!isMuted ? 'MUTE' : 'UNMUTE');
@ -184,7 +186,7 @@ class ProfileSummaryView extends PureComponent {
</TouchableOpacity> </TouchableOpacity>
</Fragment> </Fragment>
</View> </View>
{isLoggedIn && !isOwnProfile && ( {isLoggedIn && !isOwnProfile ? (
<View style={styles.rightIcons}> <View style={styles.rightIcons}>
<IconButton <IconButton
backgroundColor="transparent" backgroundColor="transparent"
@ -221,6 +223,19 @@ class ProfileSummaryView extends PureComponent {
/> />
)} )}
</View> </View>
) : (
isOwnProfile && (
<Fragment>
<IconButton
backgroundColor="transparent"
color="#c1c5c7"
iconType="MaterialCommunityIcons"
name="pencil"
onPress={handleOnPressProfileEdit}
size={20}
/>
</Fragment>
)
)} )}
</View> </View>
</Fragment> </Fragment>

View File

@ -5,9 +5,9 @@ import { connect } from 'react-redux';
// Styles // Styles
import styles from './textInputStyles'; import styles from './textInputStyles';
const TextInputView = ({ isDarkTheme, innerRef, ...props }) => ( const TextInputView = ({ isDarkTheme, innerRef, height, ...props }) => (
<TextInput <TextInput
style={styles.input} style={[styles.input, { minHeight: height }]}
ref={innerRef} ref={innerRef}
keyboardAppearance={isDarkTheme ? 'dark' : 'light'} keyboardAppearance={isDarkTheme ? 'dark' : 'light'}
{...props} {...props}

View File

@ -26,9 +26,12 @@ class UserAvatarView extends Component {
// Component Functions // Component Functions
_handleOnAvatarPress = username => { _handleOnAvatarPress = username => {
const { dispatch, currentUsername } = this.props; const {
dispatch,
currentUsername: { name },
} = this.props;
const routeName = currentUsername === username ? ROUTES.TABBAR.PROFILE : ROUTES.SCREENS.PROFILE; const routeName = name === username ? ROUTES.TABBAR.PROFILE : ROUTES.SCREENS.PROFILE;
const navigateAction = NavigationActions.navigate({ const navigateAction = NavigationActions.navigate({
routeName, routeName,
@ -42,11 +45,25 @@ class UserAvatarView extends Component {
}; };
render() { render() {
const { username, size, style, disableSize, noAction } = this.props; const {
username,
size,
style,
disableSize,
noAction,
avatarUrl,
currentUsername: { name, avatar },
} = this.props;
const imageSize = size === 'xl' ? 'large' : 'small'; const imageSize = size === 'xl' ? 'large' : 'small';
let _size; let _size;
const _avatar = username const _avatar = username
? { uri: `https://steemitimages.com/u/${username}/avatar/${imageSize}` } ? {
uri:
avatarUrl ||
(name === username
? avatar
: `https://steemitimages.com/u/${username}/avatar/${imageSize}`),
}
: DEFAULT_IMAGE; : DEFAULT_IMAGE;
if (!disableSize) { if (!disableSize) {
@ -73,7 +90,7 @@ class UserAvatarView extends Component {
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
currentUsername: state.account.currentAccount.name, currentUsername: state.account.currentAccount,
}); });
export default connect(mapStateToProps)(UserAvatarView); export default connect(mapStateToProps)(UserAvatarView);

View File

@ -97,7 +97,13 @@
"days": "days", "days": "days",
"day": "day", "day": "day",
"steem_dollars": "Steem Dollars", "steem_dollars": "Steem Dollars",
"savings": "Savings" "savings": "Savings",
"edit": {
"display_name": "Display Name",
"about": "About",
"location": "Location",
"website": "Website"
}
}, },
"settings": { "settings": {
"settings": "Settings", "settings": "Settings",

View File

@ -15,15 +15,15 @@ export default {
LOGIN: `Login${SCREEN_SUFFIX}`, LOGIN: `Login${SCREEN_SUFFIX}`,
PINCODE: `PinCode${SCREEN_SUFFIX}`, PINCODE: `PinCode${SCREEN_SUFFIX}`,
POST: `Post${SCREEN_SUFFIX}`, POST: `Post${SCREEN_SUFFIX}`,
PROFILE_EDIT: `ProfileEdit${SCREEN_SUFFIX}`,
PROFILE: `Profile${SCREEN_SUFFIX}`, PROFILE: `Profile${SCREEN_SUFFIX}`,
PROMOTE: `Promote${SCREEN_SUFFIX}`,
REBLOGS: `Reblogs${SCREEN_SUFFIX}`, REBLOGS: `Reblogs${SCREEN_SUFFIX}`,
REDEEM: `Redeem${SCREEN_SUFFIX}`,
SEARCH_RESULT: `SearchResult${SCREEN_SUFFIX}`, SEARCH_RESULT: `SearchResult${SCREEN_SUFFIX}`,
SETTINGS: `Settings${SCREEN_SUFFIX}`, SETTINGS: `Settings${SCREEN_SUFFIX}`,
STEEM_CONNECT: `SteemConnect${SCREEN_SUFFIX}`, STEEM_CONNECT: `SteemConnect${SCREEN_SUFFIX}`,
TRANSFER: `Transfer${SCREEN_SUFFIX}`, TRANSFER: `Transfer${SCREEN_SUFFIX}`,
VOTERS: `Voters${SCREEN_SUFFIX}`, VOTERS: `Voters${SCREEN_SUFFIX}`,
REDEEM: `Redeem${SCREEN_SUFFIX}`,
}, },
DRAWER: { DRAWER: {
MAIN: `Main${DRAWER_SUFFIX}`, MAIN: `Main${DRAWER_SUFFIX}`,

View File

@ -1,6 +1,7 @@
export const steemConnectOptions = { export const steemConnectOptions = {
base_url: 'https://app.steemconnect.com/', base_url: 'https://app.steemconnect.com/',
client_id: 'esteemapp', client_id: 'esteemapp',
redirect_uri: 'http://127.0.0.1:3415/', // http://127.0.0.1:3415 redirect_uri: 'http://127.0.0.1:3415/',
scope: 'vote,comment,delete_comment,comment_options,custom_json,claim_reward_balance,offline', scope:
'vote,comment,delete_comment,comment_options,custom_json,claim_reward_balance,account_update,offline',
}; };

View File

@ -1,5 +1,6 @@
import PointsContainer from './pointsContainer'; import PointsContainer from './pointsContainer';
import TransferContainer from './transferContainer'; import ProfileEditContainer from './profileEditContainer';
import RedeemContainer from './redeemContainer'; import RedeemContainer from './redeemContainer';
import TransferContainer from './transferContainer';
export { PointsContainer, TransferContainer, RedeemContainer }; export { PointsContainer, ProfileEditContainer, RedeemContainer, TransferContainer };

View File

@ -0,0 +1,214 @@
import { Component } from 'react';
import { Alert } from 'react-native';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import ImagePicker from 'react-native-image-crop-picker';
import get from 'lodash/get';
import { withNavigation } from 'react-navigation';
import { uploadImage } from '../providers/esteem/esteem';
import { profileUpdate } from '../providers/steem/dsteem';
import { updateCurrentAccount } from '../redux/actions/accountAction';
// import ROUTES from '../constants/routeNames';
const FORM_DATA = [
{
valueKey: 'name',
type: 'text',
label: 'display_name',
placeholder: '',
},
{
valueKey: 'about',
type: 'text',
label: 'about',
placeholder: '',
},
{
valueKey: 'location',
type: 'text',
label: 'location',
placeholder: '',
},
{
valueKey: 'website',
type: 'text',
label: 'website',
placeholder: '',
},
];
class ProfileEditContainer extends Component {
/* Props
* ------------------------------------------------
* @prop { type } name - Description....
*/
constructor(props) {
super(props);
this.state = {
isLoading: false,
about: get(props.currentAccount, 'about.profile.about'),
name: get(props.currentAccount, 'about.profile.name'),
location: get(props.currentAccount, 'about.profile.location'),
website: get(props.currentAccount, 'about.profile.website'),
coverUrl: get(props.currentAccount, 'about.profile.cover_image'),
avatarUrl: get(props.currentAccount, 'avatar'),
};
}
// Component Life Cycles
// Component Functions
_handleOnItemChange = (val, item) => {
this.setState({ [item]: val });
};
_uploadImage = (media, action) => {
const { intl } = this.props;
this.setState({ isLoading: true });
uploadImage(media)
.then(res => {
if (res.data && res.data.url) {
this.setState({ [action]: res.data.url, isLoading: false });
}
})
.catch(error => {
if (error) {
Alert.alert(
intl.formatMessage({
id: 'alert.fail',
}),
error.message || error.toString(),
);
}
this.setState({ isLoading: false });
});
};
_handleMediaAction = (type, uploadAction) => {
if (type === 'camera') {
this._handleOpenCamera(uploadAction);
} else if (type === 'image') {
this._handleOpenImagePicker(uploadAction);
}
};
_handleOpenImagePicker = action => {
ImagePicker.openPicker({
includeBase64: true,
})
.then(image => {
this._handleMediaOnSelected(image, action);
})
.catch(e => {
this._handleMediaOnSelectFailure(e);
});
};
_handleOpenCamera = action => {
ImagePicker.openCamera({
includeBase64: true,
})
.then(image => {
this._handleMediaOnSelected(image, action);
})
.catch(e => {
this._handleMediaOnSelectFailure(e);
});
};
_handleMediaOnSelected = (media, action) => {
this.setState({ isLoading: true }, () => {
this._uploadImage(media, action);
});
};
_handleMediaOnSelectFailure = error => {
const { intl } = this.props;
if (get(error, 'code') === 'E_PERMISSION_MISSING') {
Alert.alert(
intl.formatMessage({
id: 'alert.permission_denied',
}),
intl.formatMessage({
id: 'alert.permission_text',
}),
);
}
};
_handleOnSubmit = async () => {
const { currentAccount, pinCode, dispatch, navigation, intl } = this.props;
const { name, location, website, about, coverUrl, avatarUrl } = this.state;
await this.setState({ isLoading: true });
const params = {
profile_image: avatarUrl,
cover_image: coverUrl,
name,
website,
about,
location,
};
await profileUpdate(params, pinCode, currentAccount)
.then(async () => {
const _currentAccount = { ...currentAccount, display_name: name, avatar: avatarUrl };
_currentAccount.about.profile = { ...params };
dispatch(updateCurrentAccount(_currentAccount));
navigation.state.params.fetchUser();
navigation.goBack();
})
.catch(error => {
Alert.alert(
intl.formatMessage({
id: 'alert.fail',
}),
get(error, 'message', error.toString()),
);
});
this.setState({ isLoading: false });
};
render() {
const { children, currentAccount, isDarkTheme } = this.props;
const { isLoading, name, location, website, about, coverUrl, avatarUrl } = this.state;
return (
children &&
children({
about,
avatarUrl,
coverUrl,
currentAccount,
formData: FORM_DATA,
handleMediaAction: this._handleMediaAction,
handleOnItemChange: this._handleOnItemChange,
handleOnSubmit: this._handleOnSubmit,
isDarkTheme,
isLoading,
location,
name,
website,
})
);
}
}
const mapStateToProps = state => ({
currentAccount: state.account.currentAccount,
isDarkTheme: state.application.isDarkTheme,
pinCode: state.application.pin,
});
export default connect(mapStateToProps)(injectIntl(withNavigation(ProfileEditContainer)));

View File

@ -18,6 +18,7 @@ import {
PinCode, PinCode,
Post, Post,
Profile, Profile,
ProfileEdit,
Reblogs, Reblogs,
Redeem, Redeem,
SearchResult, SearchResult,
@ -55,6 +56,12 @@ const stackNavigatior = createStackNavigator(
header: () => null, header: () => null,
}, },
}, },
[ROUTES.SCREENS.PROFILE_EDIT]: {
screen: ProfileEdit,
navigationOptions: {
header: () => null,
},
},
[ROUTES.SCREENS.POST]: { [ROUTES.SCREENS.POST]: {
screen: Post, screen: Post,
navigationOptions: { navigationOptions: {

View File

@ -300,7 +300,14 @@ export const getImages = username => api.get(`api/images/${username}`).then(resp
export const addMyImage = (user, url) => api.post('/image', { username: user, image_url: url }); export const addMyImage = (user, url) => api.post('/image', { username: user, image_url: url });
export const uploadImage = file => { export const uploadImage = media => {
const file = {
uri: media.path,
type: media.mime,
name: media.filename || `IMG_${Math.random()}.JPG`,
size: media.size,
};
const fData = new FormData(); const fData = new FormData();
fData.append('postimage', file); fData.append('postimage', file);

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */
import { Client, PrivateKey } from 'dsteem'; import { Client, PrivateKey } from 'dsteem';
import steemconnect from 'steemconnect'; import steemconnect from 'steemconnect';
import Config from 'react-native-config'; import Config from 'react-native-config';
@ -1167,6 +1168,63 @@ export const boost = (currentAccount, pinCode, point, permlink, author) => {
return Promise.reject(new Error('Something went wrong!')); return Promise.reject(new Error('Something went wrong!'));
}; };
export const profileUpdate = async (params, pin, currentAccount) => {
const digitPinCode = getDigitPinCode(pin);
const key = getActiveKey(get(currentAccount, 'local'), digitPinCode);
if (get(currentAccount, 'local.authType') === AUTH_TYPE.STEEM_CONNECT) {
const token = decryptKey(get(currentAccount, 'local.accessToken'), digitPinCode);
const api = new steemconnect.Client({
accessToken: token,
});
const _params = {
account: get(currentAccount, 'name'),
memo_key: get(currentAccount, 'memo_key'),
json_metadata: jsonStringify(params),
};
const opArray = [['account_update', _params]];
return api
.broadcast(opArray)
.then(resp => resp.result)
.catch(error => console.log(error));
}
if (key) {
const opArray = [
[
'account_update',
{
account: get(currentAccount, 'name'),
memo_key: get(currentAccount, 'memo_key'),
json_metadata: jsonStringify({ profile: params }),
},
],
];
const privateKey = PrivateKey.fromString(key);
return new Promise((resolve, reject) => {
client.broadcast
.sendOperations(opArray, privateKey)
.then(result => {
resolve(result);
})
.catch(error => {
if (get(error, 'jse_info.code') === 4030100) {
error.message =
'We noticed that your device has incorrect date or time. Please fix Date & Time or Set Automatically and try again.';
}
reject(error);
});
});
}
return Promise.reject(new Error('You dont have permission!'));
};
// HELPERS // HELPERS
const getAnyPrivateKey = (local, pin) => { const getAnyPrivateKey = (local, pin) => {

View File

@ -487,13 +487,6 @@ class ApplicationContainer extends Component {
const isExistUser = await getExistUser(); const isExistUser = await getExistUser();
realmObject[0].name = currentUsername; realmObject[0].name = currentUsername;
dispatch(
updateCurrentAccount({
name: realmObject[0].username,
avatar: realmObject[0].avatar,
authType: realmObject[0].authType,
}),
);
// If in dev mode pin code does not show // If in dev mode pin code does not show
if ((!isExistUser || !pinCode) && _isPinCodeOpen) { if ((!isExistUser || !pinCode) && _isPinCodeOpen) {
dispatch(openPinCodeModal()); dispatch(openPinCodeModal());

View File

@ -42,7 +42,6 @@ class EditorContainer extends Component {
autoFocusText: false, autoFocusText: false,
draftId: null, draftId: null,
draftPost: null, draftPost: null,
isCameraOrPickerOpen: false,
isDraftSaved: false, isDraftSaved: false,
isDraftSaving: false, isDraftSaving: false,
isEdit: false, isEdit: false,
@ -141,8 +140,6 @@ class EditorContainer extends Component {
}; };
_handleRoutingAction = routingAction => { _handleRoutingAction = routingAction => {
this.setState({ isCameraOrPickerOpen: true });
if (routingAction === 'camera') { if (routingAction === 'camera') {
this._handleOpenCamera(); this._handleOpenCamera();
} else if (routingAction === 'image') { } else if (routingAction === 'image') {
@ -175,7 +172,7 @@ class EditorContainer extends Component {
}; };
_handleMediaOnSelected = media => { _handleMediaOnSelected = media => {
this.setState({ isCameraOrPickerOpen: false, isUploading: true }, () => { this.setState({ isUploading: true }, () => {
this._uploadImage(media); this._uploadImage(media);
}); });
// For new image api // For new image api
@ -189,33 +186,27 @@ class EditorContainer extends Component {
_uploadImage = media => { _uploadImage = media => {
const { intl } = this.props; const { intl } = this.props;
const file = { uploadImage(media)
uri: media.path,
type: media.mime,
name: media.filename || `IMG_${Math.random()}.JPG`,
size: media.size,
};
uploadImage(file)
.then(res => { .then(res => {
if (res.data && res.data.url) { if (res.data && res.data.url) {
this.setState({ uploadedImage: res.data, isUploading: false }); this.setState({ uploadedImage: res.data, isUploading: false });
} }
}) })
.catch(error => { .catch(error => {
if (error) {
Alert.alert( Alert.alert(
intl.formatMessage({ intl.formatMessage({
id: 'alert.fail', id: 'alert.fail',
}), }),
error.message || error.toString(), error.message || error.toString(),
); );
}
this.setState({ isUploading: false }); this.setState({ isUploading: false });
}); });
}; };
_handleMediaOnSelectFailure = error => { _handleMediaOnSelectFailure = error => {
const { intl } = this.props; const { intl } = this.props;
this.setState({ isCameraOrPickerOpen: false });
if (get(error, 'code') === 'E_PERMISSION_MISSING') { if (get(error, 'code') === 'E_PERMISSION_MISSING') {
Alert.alert( Alert.alert(
@ -567,7 +558,6 @@ class EditorContainer extends Component {
const { const {
autoFocusText, autoFocusText,
draftPost, draftPost,
isCameraOrPickerOpen,
isDraftSaved, isDraftSaved,
isDraftSaving, isDraftSaving,
isEdit, isEdit,
@ -589,7 +579,6 @@ class EditorContainer extends Component {
handleOnImagePicker={this._handleRoutingAction} handleOnImagePicker={this._handleRoutingAction}
handleOnSubmit={this._handleSubmit} handleOnSubmit={this._handleSubmit}
initialEditor={this._initialEditor} initialEditor={this._initialEditor}
isCameraOrPickerOpen={isCameraOrPickerOpen}
isDarkTheme={isDarkTheme} isDarkTheme={isDarkTheme}
isDraftSaved={isDraftSaved} isDraftSaved={isDraftSaved}
isDraftSaving={isDraftSaving} isDraftSaving={isDraftSaving}

View File

@ -23,8 +23,6 @@ class HomeScreen extends PureComponent {
render() { render() {
const { currentAccount, intl, isLoggedIn } = this.props; const { currentAccount, intl, isLoggedIn } = this.props;
let tag;
return ( return (
<Fragment> <Fragment>
<Header /> <Header />
@ -50,7 +48,7 @@ class HomeScreen extends PureComponent {
<Posts <Posts
filterOptions={PROFILE_FILTERS} filterOptions={PROFILE_FILTERS}
getFor={PROFILE_FILTERS[1].toLowerCase()} getFor={PROFILE_FILTERS[1].toLowerCase()}
tag={tag || currentAccount.name} tag={currentAccount.name}
selectedOptionIndex={1} selectedOptionIndex={1}
/> />
</View> </View>

View File

@ -14,16 +14,15 @@ import { Profile } from './profile';
import { SearchResult } from './searchResult'; import { SearchResult } from './searchResult';
import { Settings } from './settings'; import { Settings } from './settings';
import Voters from './voters'; import Voters from './voters';
import BoostPost from './boostPost/screen/boostPostScreen';
import SteemConnect from './steem-connect/steemConnect'; import SteemConnect from './steem-connect/steemConnect';
import Transfer from './transfer'; import Transfer from './transfer';
import Reblogs from './reblogs'; import Reblogs from './reblogs';
import ProfileEdit from './profileEdit/screen/profileEditScreen';
import Redeem from './redeem/screen/redeemScreen'; import Redeem from './redeem/screen/redeemScreen';
export { export {
Bookmarks, Bookmarks,
Boost, Boost,
BoostPost,
Drafts, Drafts,
Editor, Editor,
Follows, Follows,
@ -35,11 +34,12 @@ export {
Points, Points,
Post, Post,
Profile, Profile,
ProfileEdit,
Reblogs,
SearchResult, SearchResult,
Settings, Settings,
SteemConnect, SteemConnect,
Transfer, Transfer,
Voters, Voters,
Reblogs,
Redeem, Redeem,
}; };

View File

@ -102,7 +102,7 @@ class LoginScreen extends PureComponent {
onKeyboardWillShow={() => this.setState({ keyboardIsOpen: true })} onKeyboardWillShow={() => this.setState({ keyboardIsOpen: true })}
onKeyboardWillHide={() => this.setState({ keyboardIsOpen: false })} onKeyboardWillHide={() => this.setState({ keyboardIsOpen: false })}
enableAutoAutomaticScroll={Platform.OS === 'ios'} enableAutoAutomaticScroll={Platform.OS === 'ios'}
contentContainerStyle={{ flexGrow: 1 }} contentContainerStyle={styles.formWrapper}
> >
<FormInput <FormInput
rightIconName="at" rightIconName="at"

View File

@ -34,4 +34,9 @@ export default EStyleSheet.create({
cancelButton: { cancelButton: {
marginRight: 10, marginRight: 10,
}, },
formWrapper: {
flexGrow: 1,
marginHorizontal: 30,
marginVertical: 10,
},
}); });

View File

@ -343,6 +343,17 @@ class ProfileContainer extends Component {
this.setState({ forceLoadPost: value }); this.setState({ forceLoadPost: value });
}; };
_handleOnPressProfileEdit = () => {
const { navigation, currentAccount } = this.props;
navigation.navigate({
routeName: ROUTES.SCREENS.PROFILE_EDIT,
params: {
fetchUser: () => this.setState({ user: currentAccount }),
},
});
};
render() { render() {
const { const {
avatar, avatar,
@ -368,16 +379,19 @@ class ProfileContainer extends Component {
about={get(user, 'about.profile')} about={get(user, 'about.profile')}
activePage={activePage} activePage={activePage}
avatar={avatar} avatar={avatar}
changeForceLoadPostState={this._changeForceLoadPostState}
comments={comments} comments={comments}
currency={currency} currency={currency}
error={error} error={error}
follows={follows} follows={follows}
forceLoadPost={forceLoadPost}
getReplies={() => this._getReplies(username)} getReplies={() => this._getReplies(username)}
handleFollowUnfollowUser={this._handleFollowUnfollowUser} handleFollowUnfollowUser={this._handleFollowUnfollowUser}
handleMuteUnmuteUser={this._handleMuteUnmuteUser} handleMuteUnmuteUser={this._handleMuteUnmuteUser}
handleOnBackPress={this._handleOnBackPress} handleOnBackPress={this._handleOnBackPress}
handleOnFavoritePress={this._handleOnFavoritePress} handleOnFavoritePress={this._handleOnFavoritePress}
handleOnFollowsPress={this._handleFollowsPress} handleOnFollowsPress={this._handleFollowsPress}
handleOnPressProfileEdit={this._handleOnPressProfileEdit}
isDarkTheme={isDarkTheme} isDarkTheme={isDarkTheme}
isFavorite={isFavorite} isFavorite={isFavorite}
isFollowing={isFollowing} isFollowing={isFollowing}
@ -389,8 +403,6 @@ class ProfileContainer extends Component {
selectedQuickProfile={selectedQuickProfile} selectedQuickProfile={selectedQuickProfile}
selectedUser={user} selectedUser={user}
username={username} username={username}
forceLoadPost={forceLoadPost}
changeForceLoadPostState={this._changeForceLoadPostState}
/> />
); );
} }

View File

@ -62,15 +62,19 @@ class ProfileScreen extends PureComponent {
render() { render() {
const { const {
about, about,
activePage,
changeForceLoadPostState,
comments, comments,
currency, currency,
follows, follows,
forceLoadPost,
getReplies, getReplies,
handleFollowUnfollowUser, handleFollowUnfollowUser,
handleMuteUnmuteUser, handleMuteUnmuteUser,
handleOnBackPress, handleOnBackPress,
handleOnFavoritePress, handleOnFavoritePress,
handleOnFollowsPress, handleOnFollowsPress,
handleOnPressProfileEdit,
intl, intl,
isDarkTheme, isDarkTheme,
isFavorite, isFavorite,
@ -83,9 +87,6 @@ class ProfileScreen extends PureComponent {
selectedQuickProfile, selectedQuickProfile,
selectedUser, selectedUser,
username, username,
activePage,
forceLoadPost,
changeForceLoadPostState,
} = this.props; } = this.props;
const { const {
@ -175,6 +176,7 @@ class ProfileScreen extends PureComponent {
location={location} location={location}
percentRC={resourceCredits} percentRC={resourceCredits}
percentVP={votingPower} percentVP={votingPower}
handleOnPressProfileEdit={handleOnPressProfileEdit}
/> />
</CollapsibleCard> </CollapsibleCard>
)} )}

View File

@ -0,0 +1,104 @@
import React, { PureComponent, Fragment } from 'react';
import { injectIntl } from 'react-intl';
import get from 'lodash/get';
import ActionSheet from 'react-native-actionsheet';
import { ProfileEditContainer } from '../../../containers';
import AvatarHeader from '../../../components/avatarHeader/avatarHeaderView';
import ProfileEditForm from '../../../components/profileEditForm/profileEditFormView';
class ProfileEditScreen extends PureComponent {
/* Props
* ------------------------------------------------
* @prop { type } name - Description....
*/
constructor(props) {
super(props);
this.state = {
selectedUploadAction: '',
};
this.galleryRef = React.createRef();
}
// Component Life Cycles
// Component Functions
_showImageUploadActions = async action => {
await this.setState({ selectedUploadAction: action });
this.galleryRef.current.show();
};
render() {
const { intl } = this.props;
const { selectedUploadAction } = this.state;
return (
<ProfileEditContainer>
{({
currentAccount,
isDarkTheme,
formData,
handleOnItemChange,
handleMediaAction,
name,
location,
website,
about,
avatarUrl,
coverUrl,
isLoading,
handleOnSubmit,
}) => (
<Fragment>
<AvatarHeader
username={get(currentAccount, 'name')}
name={name}
reputation={get(currentAccount, 'reputation')}
avatarUrl={avatarUrl}
showImageUploadActions={() => this._showImageUploadActions('avatarUrl')}
/>
<ProfileEditForm
formData={formData}
isDarkTheme={isDarkTheme}
about={about}
name={name}
location={location}
website={website}
coverUrl={coverUrl}
showImageUploadActions={() => this._showImageUploadActions('coverUrl')}
handleOnItemChange={handleOnItemChange}
isLoading={isLoading}
handleOnSubmit={handleOnSubmit}
/>
<ActionSheet
ref={this.galleryRef}
options={[
intl.formatMessage({
id: 'editor.open_gallery',
}),
intl.formatMessage({
id: 'editor.capture_photo',
}),
intl.formatMessage({
id: 'alert.cancel',
}),
]}
cancelButtonIndex={2}
onPress={index => {
handleMediaAction(
index === 0 ? 'image' : index === 1 && 'camera',
selectedUploadAction,
);
}}
/>
</Fragment>
)}
</ProfileEditContainer>
);
}
}
export default injectIntl(ProfileEditScreen);

View File

@ -84,15 +84,6 @@ class SettingsScreen extends PureComponent {
})} })}
titleStyle={styles.cardTitle} titleStyle={styles.cardTitle}
/> />
<SettingsItem
title={intl.formatMessage({
id: 'settings.dark_theme',
})}
type="toggle"
actionType="theme"
isOn={isDarkTheme}
handleOnChange={handleOnChange}
/>
<SettingsItem <SettingsItem
title={intl.formatMessage({ title={intl.formatMessage({
id: 'settings.currency', id: 'settings.currency',
@ -143,6 +134,15 @@ class SettingsScreen extends PureComponent {
selectedOptionIndex={parseInt(nsfw, 10)} selectedOptionIndex={parseInt(nsfw, 10)}
handleOnChange={handleOnChange} handleOnChange={handleOnChange}
/> />
<SettingsItem
title={intl.formatMessage({
id: 'settings.dark_theme',
})}
type="toggle"
actionType="theme"
isOn={isDarkTheme}
handleOnChange={handleOnChange}
/>
{!!isLoggedIn && ( {!!isLoggedIn && (
<SettingsItem <SettingsItem
title={intl.formatMessage({ title={intl.formatMessage({

View File

@ -1,6 +0,0 @@
export const steemConnectOptions = {
base_url: 'https://steemconnect.com/oauth2/authorize',
client_id: 'esteem-app',
redirect_uri: 'http://127.0.0.1:3415/', // http://127.0.0.1:3415
scope: 'vote,comment,delete_comment,comment_options,custom_json,claim_reward_balance,offline',
};

View File

@ -5,7 +5,7 @@ import { injectIntl } from 'react-intl';
import { withNavigation } from 'react-navigation'; import { withNavigation } from 'react-navigation';
import { loginWithSC2 } from '../../providers/steem/auth'; import { loginWithSC2 } from '../../providers/steem/auth';
import { steemConnectOptions } from './config'; import { steemConnectOptions } from '../../constants/steemConnectOptions';
// Actions // Actions
import { addOtherAccount, updateCurrentAccount } from '../../redux/actions/accountAction'; import { addOtherAccount, updateCurrentAccount } from '../../redux/actions/accountAction';
@ -79,7 +79,7 @@ class SteemConnect extends PureComponent {
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<WebView <WebView
source={{ source={{
uri: `${steemConnectOptions.base_url}?client_id=${ uri: `${steemConnectOptions.base_url}oauth2/authorize?client_id=${
steemConnectOptions.client_id steemConnectOptions.client_id
}&redirect_uri=${encodeURIComponent( }&redirect_uri=${encodeURIComponent(
steemConnectOptions.redirect_uri, steemConnectOptions.redirect_uri,

View File

@ -63,3 +63,12 @@ export const catchDraftImage = body => {
} }
return null; return null;
}; };
export const getResizedImage = (url, size) => {
if (!url) return '';
const _size = size || 400;
if (url.includes('img.esteem')) return `https://img.esteem.ws/${_size}x0/${url}`;
return `https://steemitimages.com/${size}x0/${url}`;
};