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
iconStyle={[styles.backIcon, isModalHeader && styles.closeIcon]}
iconType="MaterialIcons"
name={isModalHeader ? 'arrow-back' : 'arrow-back'}
name="arrow-back"
onPress={() => (isModalHeader ? handleOnPressClose() : handleOnPressBackButton())}
disabled={disabled}
/>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,15 +3,16 @@ import { FormInput } from './formInput';
import { NumericKeyboard } from './numericKeyboard';
import { PinAnimatedInput } from './pinAnimatedInput';
import { SideMenu } from './sideMenu';
import { TextInput } from './textInput';
import Icon from './icon';
import Logo from './logo/logo';
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 UserListItem from './basicUIElements/view/userListItem/userListItem';
import PostButton from './postButton/postButtonView';
import Promote from './promote/promoteView';
import PostBoost from './postBoost/postBoostView';
export {
CircularButton,
@ -22,12 +23,13 @@ export {
Modal,
NumericKeyboard,
PinAnimatedInput,
PostButton,
ProfileEditForm,
ScaleSlider,
SideMenu,
TextButton,
TextInput,
UserListItem,
PostButton,
Promote,
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
import { makeCountFriendly } from '../../../utils/formatter';
import { getResizedImage } from '../../../utils/image';
// Styles
import styles from './profileSummaryStyles';
@ -67,6 +68,7 @@ class ProfileSummaryView extends PureComponent {
handleFollowUnfollowUser,
handleOnFavoritePress,
handleOnFollowsPress,
handleOnPressProfileEdit,
handleUIChange,
hoursRC,
hoursVP,
@ -94,7 +96,7 @@ class ProfileSummaryView extends PureComponent {
const isColumn = rowLength && DEVICE_WIDTH / rowLength <= 7.3;
const followButtonIcon = !isFollowing ? 'account-plus' : 'account-minus';
const coverImageUrl = `https://steemitimages.com/400x0/${coverImage}`;
const coverImageUrl = getResizedImage(coverImage, 400);
dropdownOpions.push(!isMuted ? 'MUTE' : 'UNMUTE');
@ -184,7 +186,7 @@ class ProfileSummaryView extends PureComponent {
</TouchableOpacity>
</Fragment>
</View>
{isLoggedIn && !isOwnProfile && (
{isLoggedIn && !isOwnProfile ? (
<View style={styles.rightIcons}>
<IconButton
backgroundColor="transparent"
@ -221,6 +223,19 @@ class ProfileSummaryView extends PureComponent {
/>
)}
</View>
) : (
isOwnProfile && (
<Fragment>
<IconButton
backgroundColor="transparent"
color="#c1c5c7"
iconType="MaterialCommunityIcons"
name="pencil"
onPress={handleOnPressProfileEdit}
size={20}
/>
</Fragment>
)
)}
</View>
</Fragment>

View File

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

View File

@ -26,9 +26,12 @@ class UserAvatarView extends Component {
// Component Functions
_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({
routeName,
@ -42,11 +45,25 @@ class UserAvatarView extends Component {
};
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';
let _size;
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;
if (!disableSize) {
@ -73,7 +90,7 @@ class UserAvatarView extends Component {
}
const mapStateToProps = state => ({
currentUsername: state.account.currentAccount.name,
currentUsername: state.account.currentAccount,
});
export default connect(mapStateToProps)(UserAvatarView);

View File

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

View File

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

View File

@ -1,6 +1,7 @@
export const steemConnectOptions = {
base_url: 'https://app.steemconnect.com/',
client_id: 'esteemapp',
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',
redirect_uri: 'http://127.0.0.1:3415/',
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 TransferContainer from './transferContainer';
import ProfileEditContainer from './profileEditContainer';
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,
Post,
Profile,
ProfileEdit,
Reblogs,
Redeem,
SearchResult,
@ -55,6 +56,12 @@ const stackNavigatior = createStackNavigator(
header: () => null,
},
},
[ROUTES.SCREENS.PROFILE_EDIT]: {
screen: ProfileEdit,
navigationOptions: {
header: () => null,
},
},
[ROUTES.SCREENS.POST]: {
screen: Post,
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 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();
fData.append('postimage', file);

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */
import { Client, PrivateKey } from 'dsteem';
import steemconnect from 'steemconnect';
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!'));
};
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
const getAnyPrivateKey = (local, pin) => {

View File

@ -487,13 +487,6 @@ class ApplicationContainer extends Component {
const isExistUser = await getExistUser();
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 ((!isExistUser || !pinCode) && _isPinCodeOpen) {
dispatch(openPinCodeModal());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,15 +62,19 @@ class ProfileScreen extends PureComponent {
render() {
const {
about,
activePage,
changeForceLoadPostState,
comments,
currency,
follows,
forceLoadPost,
getReplies,
handleFollowUnfollowUser,
handleMuteUnmuteUser,
handleOnBackPress,
handleOnFavoritePress,
handleOnFollowsPress,
handleOnPressProfileEdit,
intl,
isDarkTheme,
isFavorite,
@ -83,9 +87,6 @@ class ProfileScreen extends PureComponent {
selectedQuickProfile,
selectedUser,
username,
activePage,
forceLoadPost,
changeForceLoadPostState,
} = this.props;
const {
@ -175,6 +176,7 @@ class ProfileScreen extends PureComponent {
location={location}
percentRC={resourceCredits}
percentVP={votingPower}
handleOnPressProfileEdit={handleOnPressProfileEdit}
/>
</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}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.dark_theme',
})}
type="toggle"
actionType="theme"
isOn={isDarkTheme}
handleOnChange={handleOnChange}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.currency',
@ -143,6 +134,15 @@ class SettingsScreen extends PureComponent {
selectedOptionIndex={parseInt(nsfw, 10)}
handleOnChange={handleOnChange}
/>
<SettingsItem
title={intl.formatMessage({
id: 'settings.dark_theme',
})}
type="toggle"
actionType="theme"
isOn={isDarkTheme}
handleOnChange={handleOnChange}
/>
{!!isLoggedIn && (
<SettingsItem
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 { loginWithSC2 } from '../../providers/steem/auth';
import { steemConnectOptions } from './config';
import { steemConnectOptions } from '../../constants/steemConnectOptions';
// Actions
import { addOtherAccount, updateCurrentAccount } from '../../redux/actions/accountAction';
@ -79,7 +79,7 @@ class SteemConnect extends PureComponent {
<View style={{ flex: 1 }}>
<WebView
source={{
uri: `${steemConnectOptions.base_url}?client_id=${
uri: `${steemConnectOptions.base_url}oauth2/authorize?client_id=${
steemConnectOptions.client_id
}&redirect_uri=${encodeURIComponent(
steemConnectOptions.redirect_uri,

View File

@ -63,3 +63,12 @@ export const catchDraftImage = body => {
}
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}`;
};