diff --git a/src/components/avatarHeader/avatarHeaderStyles.js b/src/components/avatarHeader/avatarHeaderStyles.js
index 1840141d8..9acafbef3 100644
--- a/src/components/avatarHeader/avatarHeaderStyles.js
+++ b/src/components/avatarHeader/avatarHeaderStyles.js
@@ -2,9 +2,11 @@ import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
headerContainer: {
- height: 100,
flexDirection: 'row',
- padding: 21,
+ paddingTop: 8,
+ paddingHorizontal: 24,
+ paddingBottom: 24,
+ alignItems: 'center',
},
backIcon: {
color: '$white',
@@ -16,7 +18,7 @@ export default EStyleSheet.create({
alignItems: 'center',
},
textWrapper: {
- marginLeft: 16,
+ marginLeft: 24,
},
name: {
color: '$white',
diff --git a/src/components/avatarHeader/avatarHeaderView.js b/src/components/avatarHeader/avatarHeaderView.js
index c1f0111f2..32959effd 100644
--- a/src/components/avatarHeader/avatarHeaderView.js
+++ b/src/components/avatarHeader/avatarHeaderView.js
@@ -1,6 +1,6 @@
import React from 'react';
import { withNavigation } from 'react-navigation';
-import { View, Text, SafeAreaView } from 'react-native';
+import { View, Text, SafeAreaView, TouchableOpacity } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { UserAvatar } from '../userAvatar';
@@ -16,6 +16,7 @@ const AvatarHeader = ({
navigation,
avatarUrl,
showImageUploadActions,
+ isUploading,
}) => (
-
-
+
+
+
+
+
{!!name && {name}}
{`@${username} (${reputation})`}
diff --git a/src/components/profileEditForm/profileEditFormStyles.js b/src/components/profileEditForm/profileEditFormStyles.ts
similarity index 84%
rename from src/components/profileEditForm/profileEditFormStyles.js
rename to src/components/profileEditForm/profileEditFormStyles.ts
index f25d66add..ccd3b45b2 100644
--- a/src/components/profileEditForm/profileEditFormStyles.js
+++ b/src/components/profileEditForm/profileEditFormStyles.ts
@@ -13,6 +13,7 @@ export default EStyleSheet.create({
marginTop: 8,
},
label: {
+ marginTop:8,
fontSize: 14,
color: '$primaryDarkText',
fontWeight: '500',
@@ -25,7 +26,7 @@ export default EStyleSheet.create({
height: 60,
marginBottom: 12,
alignSelf: 'stretch',
- backgroundColor: '#296CC0',
+ backgroundColor: '$primaryGray',
},
coverImageWrapper: {},
addIcon: {
@@ -62,13 +63,19 @@ export default EStyleSheet.create({
},
input: {
- fontSize: 14,
- color: '$primaryDarkText',
+ fontSize: 20,
+ color: '$primaryBlack',
alignSelf: 'flex-start',
width: '100%',
- height: 40,
+ paddingBottom:10,
},
contentContainer: {
flexGrow: 1,
},
+ activityIndicator: {
+ position:'absolute',
+ alignSelf:'center',
+ top:0,
+ bottom:8
+ }
});
diff --git a/src/components/profileEditForm/profileEditFormView.js b/src/components/profileEditForm/profileEditFormView.tsx
similarity index 55%
rename from src/components/profileEditForm/profileEditFormView.js
rename to src/components/profileEditForm/profileEditFormView.tsx
index 982973e93..41d43a9a6 100644
--- a/src/components/profileEditForm/profileEditFormView.js
+++ b/src/components/profileEditForm/profileEditFormView.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { withNavigation } from 'react-navigation';
-import { View, TouchableOpacity, Image, Text, Platform } from 'react-native';
+import { View, TouchableOpacity, Text, Platform, ActivityIndicator } from 'react-native';
+import { View as AnimatedView } from 'react-native-animatable';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { injectIntl } from 'react-intl';
@@ -17,9 +18,25 @@ import { getResizedImage } from '../../utils/image';
// Styles
import styles from './profileEditFormStyles';
+import FastImage from 'react-native-fast-image';
+import EStyleSheet from 'react-native-extended-stylesheet';
+import { MainButton } from '../mainButton';
+
+
+interface ProfileEditFormProps {
+ coverUrl:string;
+ formData:any;
+ handleOnItemChange:()=>void;
+ handleOnSubmit:()=>void;
+ intl:any,
+ isDarkTheme:boolean,
+ isLoading:boolean,
+ isUploading:boolean,
+ showImageUploadActions:boolean,
+ saveEnabled:boolean,
+}
const ProfileEditFormView = ({
- avatarUrl,
coverUrl,
formData,
handleOnItemChange,
@@ -27,30 +44,41 @@ const ProfileEditFormView = ({
intl,
isDarkTheme,
isLoading,
+ isUploading,
showImageUploadActions,
+ saveEnabled,
...props
-}) => (
+}:ProfileEditFormProps) => (
+
-
+
-
+
+
+ {
+ isUploading && (
+
+ )
+ }
+
))}
+
+ {saveEnabled && (
+
+
+
+ )}
+
+
);
diff --git a/src/components/userAvatar/view/userAvatarStyles.js b/src/components/userAvatar/view/userAvatarStyles.ts
similarity index 66%
rename from src/components/userAvatar/view/userAvatarStyles.js
rename to src/components/userAvatar/view/userAvatarStyles.ts
index 0b6c0ae56..b8578471d 100644
--- a/src/components/userAvatar/view/userAvatarStyles.js
+++ b/src/components/userAvatar/view/userAvatarStyles.ts
@@ -6,4 +6,10 @@ export default EStyleSheet.create({
borderColor: '$borderColor',
backgroundColor: '$pureWhite',
},
+ activityIndicator: {
+ position:'absolute',
+ alignSelf:'center',
+ top:0,
+ bottom:0
+ }
});
diff --git a/src/components/userAvatar/view/userAvatarView.js b/src/components/userAvatar/view/userAvatarView.js
deleted file mode 100644
index d761c1d9a..000000000
--- a/src/components/userAvatar/view/userAvatarView.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import React, { Component } from 'react';
-import { TouchableOpacity } from 'react-native';
-import { connect } from 'react-redux';
-
-import FastImage from 'react-native-fast-image';
-import styles from './userAvatarStyles';
-import { navigate } from '../../../navigation/service';
-
-// Constants
-import ROUTES from '../../../constants/routeNames';
-
-// Utils
-import { getResizedAvatar } from '../../../utils/image';
-
-const DEFAULT_IMAGE = require('../../../assets/avatar_default.png');
-
-/* Props
- * ------------------------------------------------
- * @prop { type } name - Description....
- */
-
-class UserAvatarView extends Component {
- // Component Life Cycles
- shouldComponentUpdate(nextProps) {
- const { username } = this.props;
-
- return nextProps.username !== username;
- }
-
- // Component Functions
- _handleOnAvatarPress = (username) => {
- const {
- currentUsername: { name },
- } = this.props;
-
- const routeName = name === username ? ROUTES.TABBAR.PROFILE : ROUTES.SCREENS.PROFILE;
-
- navigate({
- routeName,
- params: {
- username,
- },
- key: username,
- });
- };
-
- render() {
- const {
- username,
- size,
- style,
- disableSize,
- noAction,
- currentUsername: { name, avatar },
- } = this.props;
- const imageSize = 'large';
- let _size;
- const _avatar = username
- ? {
- uri: getResizedAvatar(username, imageSize),
- }
- : DEFAULT_IMAGE;
-
- if (!disableSize) {
- _size = 32;
-
- if (size === 'xl') {
- _size = 64;
- }
- }
-
- return (
- this._handleOnAvatarPress(username)}>
-
-
- );
- }
-}
-
-const mapStateToProps = (state) => ({
- currentUsername: state.account.currentAccount,
-});
-
-export default connect(mapStateToProps)(UserAvatarView);
diff --git a/src/components/userAvatar/view/userAvatarView.tsx b/src/components/userAvatar/view/userAvatarView.tsx
new file mode 100644
index 000000000..d382ff5d6
--- /dev/null
+++ b/src/components/userAvatar/view/userAvatarView.tsx
@@ -0,0 +1,98 @@
+import React, { Component } from 'react';
+import { ActivityIndicator, TouchableOpacity, ViewStyle } from 'react-native';
+import { connect } from 'react-redux';
+
+import FastImage from 'react-native-fast-image';
+import styles from './userAvatarStyles';
+import { navigate } from '../../../navigation/service';
+
+// Constants
+import ROUTES from '../../../constants/routeNames';
+
+// Utils
+import { getResizedAvatar } from '../../../utils/image';
+import { useAppSelector } from '../../../hooks';
+import EStyleSheet from 'react-native-extended-stylesheet';
+
+const DEFAULT_IMAGE = require('../../../assets/avatar_default.png');
+
+/* Props
+ * ------------------------------------------------
+ * @prop { type } name - Description....
+ */
+
+interface UserAvatarProps {
+ username:string;
+ avatarUrl?:string;
+ size?:'xl';
+ style?:ViewStyle;
+ disableSize?:boolean;
+ noAction?:boolean;
+ isLoading?:boolean;
+}
+
+const UserAvatarView = ({
+ username,
+ avatarUrl,
+ size,
+ style,
+ disableSize,
+ noAction,
+ isLoading
+}:UserAvatarProps) => {
+
+ const curUsername = useAppSelector(state=>state.account.currentAccount.name);
+ const avatarCacheStamp = useAppSelector(state=>state.ui.avatarCacheStamp);
+
+ // Component Functions
+ const _handleOnAvatarPress = (username:string) => {
+ const routeName = curUsername === username ? ROUTES.TABBAR.PROFILE : ROUTES.SCREENS.PROFILE;
+ navigate({
+ routeName,
+ params: {
+ username,
+ },
+ key: username,
+ });
+ };
+
+
+ const uri = avatarUrl ? avatarUrl : getResizedAvatar(username, 'large');
+
+ const _avatar = username
+ ? { uri : `${uri}?stamp=${avatarCacheStamp}` }
+ : DEFAULT_IMAGE;
+
+ let _size:number;
+ if (!disableSize) {
+ _size = 32;
+ if (size === 'xl') {
+ _size = 64;
+ }
+ }
+
+
+ return (
+ _handleOnAvatarPress(username)}>
+
+ {
+ isLoading && (
+
+ )
+ }
+
+ );
+}
+
+export default UserAvatarView;
diff --git a/src/containers/profileEditContainer.js b/src/containers/profileEditContainer.js
index 2b4c55d7f..41602d372 100644
--- a/src/containers/profileEditContainer.js
+++ b/src/containers/profileEditContainer.js
@@ -10,6 +10,7 @@ import { uploadImage } from '../providers/ecency/ecency';
import { profileUpdate, signImage } from '../providers/hive/dhive';
import { updateCurrentAccount } from '../redux/actions/accountAction';
+import { setAvatarCacheStamp } from '../redux/actions/uiAction';
// import ROUTES from '../constants/routeNames';
@@ -50,6 +51,8 @@ class ProfileEditContainer extends Component {
super(props);
this.state = {
isLoading: false,
+ isUploading: false,
+ saveEnabled: false,
about: get(props.currentAccount, 'about.profile.about'),
name: get(props.currentAccount, 'about.profile.name'),
location: get(props.currentAccount, 'about.profile.location'),
@@ -64,20 +67,20 @@ class ProfileEditContainer extends Component {
// Component Functions
_handleOnItemChange = (val, item) => {
- this.setState({ [item]: val });
+ this.setState({ [item]: val, saveEnabled: true });
};
_uploadImage = async (media, action) => {
const { intl, currentAccount, pinCode } = this.props;
- this.setState({ isLoading: true });
+ this.setState({ isUploading: true });
let sign = await signImage(media, currentAccount, pinCode);
uploadImage(media, currentAccount.name, sign)
.then((res) => {
if (res.data && res.data.url) {
- this.setState({ [action]: res.data.url, isLoading: false });
+ this.setState({ [action]: res.data.url, isUploading: false, saveEnabled: true });
}
})
.catch((error) => {
@@ -89,7 +92,7 @@ class ProfileEditContainer extends Component {
error.message || error.toString(),
);
}
- this.setState({ isLoading: false });
+ this.setState({ isUploading: false });
});
};
@@ -102,11 +105,11 @@ class ProfileEditContainer extends Component {
};
_handleOpenImagePicker = (action) => {
- ImagePicker.openPicker({
- includeBase64: true,
- })
- .then((image) => {
- this._handleMediaOnSelected(image, action);
+ ImagePicker.openPicker(
+ action == 'avatarUrl' ? IMAGE_PICKER_AVATAR_OPTIONS : IMAGE_PICKER_COVER_OPTIONS,
+ )
+ .then((media) => {
+ this._uploadImage(media, action);
})
.catch((e) => {
this._handleMediaOnSelectFailure(e);
@@ -114,23 +117,17 @@ class ProfileEditContainer extends Component {
};
_handleOpenCamera = (action) => {
- ImagePicker.openCamera({
- includeBase64: true,
- })
- .then((image) => {
- this._handleMediaOnSelected(image, action);
+ ImagePicker.openCamera(
+ action == 'avatarUrl' ? IMAGE_PICKER_AVATAR_OPTIONS : IMAGE_PICKER_COVER_OPTIONS,
+ )
+ .then((media) => {
+ this._uploadImage(media, action);
})
.catch((e) => {
this._handleMediaOnSelectFailure(e);
});
};
- _handleMediaOnSelected = (media, action) => {
- this.setState({ isLoading: true }, () => {
- this._uploadImage(media, action);
- });
- };
-
_handleMediaOnSelectFailure = (error) => {
const { intl } = this.props;
@@ -150,7 +147,7 @@ class ProfileEditContainer extends Component {
const { currentAccount, pinCode, dispatch, navigation, intl } = this.props;
const { name, location, website, about, coverUrl, avatarUrl } = this.state;
- await this.setState({ isLoading: true });
+ this.setState({ isLoading: true });
const params = {
profile_image: avatarUrl,
@@ -161,31 +158,42 @@ class ProfileEditContainer extends Component {
location,
version: 2,
};
- await profileUpdate(params, pinCode, currentAccount)
- .then(async () => {
- const _currentAccount = { ...currentAccount, display_name: name, avatar: avatarUrl };
- _currentAccount.about.profile = { ...params };
- dispatch(updateCurrentAccount(_currentAccount));
+ try {
+ await profileUpdate(params, pinCode, currentAccount);
- navigation.state.params.fetchUser();
- navigation.goBack();
- })
- .catch((error) => {
- Alert.alert(
- intl.formatMessage({
- id: 'alert.fail',
- }),
- get(error, 'message', error.toString()),
- );
- });
+ const _currentAccount = { ...currentAccount, display_name: name, avatar: avatarUrl };
+ _currentAccount.about.profile = { ...params };
- this.setState({ isLoading: false });
+ dispatch(updateCurrentAccount(_currentAccount));
+ dispatch(setAvatarCacheStamp(new Date().getTime()));
+ this.setState({ isLoading: false });
+ navigation.state.params.fetchUser();
+ navigation.goBack();
+ } catch (err) {
+ 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;
+ const {
+ isLoading,
+ isUploading,
+ name,
+ location,
+ website,
+ about,
+ coverUrl,
+ avatarUrl,
+ saveEnabled,
+ } = this.state;
return (
children &&
@@ -200,9 +208,11 @@ class ProfileEditContainer extends Component {
handleOnSubmit: this._handleOnSubmit,
isDarkTheme,
isLoading,
+ isUploading,
location,
name,
website,
+ saveEnabled,
})
);
}
@@ -215,3 +225,14 @@ const mapStateToProps = (state) => ({
});
export default connect(mapStateToProps)(injectIntl(withNavigation(ProfileEditContainer)));
+
+const IMAGE_PICKER_AVATAR_OPTIONS = {
+ includeBase64: true,
+ cropping: true,
+ width: 512,
+ height: 512,
+};
+
+const IMAGE_PICKER_COVER_OPTIONS = {
+ includeBase64: true,
+};
diff --git a/src/redux/actions/uiAction.js b/src/redux/actions/uiAction.ts
similarity index 53%
rename from src/redux/actions/uiAction.js
rename to src/redux/actions/uiAction.ts
index 40314d795..b2a5aa265 100644
--- a/src/redux/actions/uiAction.js
+++ b/src/redux/actions/uiAction.ts
@@ -1,3 +1,4 @@
+import { ButtonProps } from 'react-native';
import {
TOAST_NOTIFICATION,
UPDATE_ACTIVE_BOTTOM_TAB,
@@ -6,19 +7,20 @@ import {
TOGGLE_ACCOUNTS_BOTTOM_SHEET,
SHOW_ACTION_MODAL,
HIDE_ACTION_MODAL,
+ SET_AVATAR_CACHE_STAMP,
} from '../constants/constants';
-export const updateActiveBottomTab = (payload) => ({
+export const updateActiveBottomTab = (payload:string) => ({
payload,
type: UPDATE_ACTIVE_BOTTOM_TAB,
});
-export const toastNotification = (payload) => ({
+export const toastNotification = (payload:string) => ({
payload,
type: TOAST_NOTIFICATION,
});
-export const showActionModal = (title, body, buttons, headerImage, onClosed) => ({
+export const showActionModal = (title:string, body:string, buttons:ButtonProps[], headerImage:any, onClosed:()=>void) => ({
payload: {
actionModalVisible: true,
actionModalData: {
@@ -36,17 +38,22 @@ export const hideActionModal = () => ({
type: HIDE_ACTION_MODAL,
});
-export const setRcOffer = (payload) => ({
+export const setRcOffer = (payload:boolean) => ({
payload,
type: RC_OFFER,
});
-export const hidePostsThumbnails = (payload) => ({
+export const hidePostsThumbnails = (payload:boolean) => ({
payload,
type: HIDE_POSTS_THUMBNAILS,
});
-export const toggleAccountsBottomSheet = (payload) => ({
+export const toggleAccountsBottomSheet = (payload:boolean) => ({
payload,
type: TOGGLE_ACCOUNTS_BOTTOM_SHEET,
});
+
+export const setAvatarCacheStamp = (payload:number) => ({
+ payload,
+ type:SET_AVATAR_CACHE_STAMP
+})
diff --git a/src/redux/constants/constants.js b/src/redux/constants/constants.js
index d22cf9a92..8c5de7830 100644
--- a/src/redux/constants/constants.js
+++ b/src/redux/constants/constants.js
@@ -54,6 +54,7 @@ export const RC_OFFER = 'RC_OFFER';
export const TOGGLE_ACCOUNTS_BOTTOM_SHEET = 'TOGGLE_ACCOUNTS_BOTTOM_SHEET';
export const SHOW_ACTION_MODAL = 'SHOW_ACTION_MODAL';
export const HIDE_ACTION_MODAL = 'HIDE_ACTION_MODAL';
+export const SET_AVATAR_CACHE_STAMP = 'SET_AVATAR_CACHE_STAMP';
// POSTS
export const SET_FEED_POSTS = 'SET_FEED_POSTS';
diff --git a/src/redux/reducers/uiReducer.js b/src/redux/reducers/uiReducer.ts
similarity index 76%
rename from src/redux/reducers/uiReducer.js
rename to src/redux/reducers/uiReducer.ts
index 783bcdb62..b792ac16d 100644
--- a/src/redux/reducers/uiReducer.js
+++ b/src/redux/reducers/uiReducer.ts
@@ -6,9 +6,21 @@ import {
TOGGLE_ACCOUNTS_BOTTOM_SHEET,
SHOW_ACTION_MODAL,
HIDE_ACTION_MODAL,
+ SET_AVATAR_CACHE_STAMP,
} from '../constants/constants';
-const initialState = {
+interface UiState {
+ activeBottomTab:string;
+ toastNotification:string;
+ hidePostsThumbnails:boolean;
+ rcOffer:boolean;
+ isVisibleAccountsBottomSheet:boolean;
+ actionModalVisible:boolean;
+ actionModalData:any;
+ avatarCacheStamp:number
+}
+
+const initialState:UiState = {
activeBottomTab: 'HomeTabbar',
toastNotification: '',
hidePostsThumbnails: false,
@@ -16,6 +28,7 @@ const initialState = {
isVisibleAccountsBottomSheet: false,
actionModalVisible: false,
actionModalData: null,
+ avatarCacheStamp: 0
};
export default function (state = initialState, action) {
@@ -65,6 +78,11 @@ export default function (state = initialState, action) {
...state,
isVisibleAccountsBottomSheet: action.payload,
};
+ case SET_AVATAR_CACHE_STAMP:
+ return {
+ ...state,
+ avatarCacheStamp: action.payload
+ }
default:
return state;
}
diff --git a/src/screens/application/container/applicationContainer.js b/src/screens/application/container/applicationContainer.js
index 7b745c082..04ecebfc7 100644
--- a/src/screens/application/container/applicationContainer.js
+++ b/src/screens/application/container/applicationContainer.js
@@ -77,6 +77,7 @@ import {
} from '../../../redux/actions/applicationActions';
import {
hideActionModal,
+ setAvatarCacheStamp,
setRcOffer,
toastNotification,
updateActiveBottomTab,
@@ -126,7 +127,6 @@ class ApplicationContainer extends Component {
this._setNetworkListener();
Linking.addEventListener('url', this._handleOpenURL);
-
Linking.getInitialURL().then((url) => {
this._handleDeepLink(url);
});
@@ -145,6 +145,9 @@ class ApplicationContainer extends Component {
if (!isIos) BackHandler.addEventListener('hardwareBackPress', this._onBackPress);
+ //set avatar cache stamp to invalidate previous session avatars
+ dispatch(setAvatarCacheStamp(new Date().getTime()));
+
getVersionForWelcomeModal().then((version) => {
if (version < parseVersionNumber(appVersion)) {
getUserData().then((accounts) => {
diff --git a/src/screens/profileEdit/screen/profileEditScreen.js b/src/screens/profileEdit/screen/profileEditScreen.js
index 1971537ca..fa71e0702 100644
--- a/src/screens/profileEdit/screen/profileEditScreen.js
+++ b/src/screens/profileEdit/screen/profileEditScreen.js
@@ -1,4 +1,5 @@
import React, { PureComponent, Fragment } from 'react';
+import { StatusBar } from 'react-native';
import { injectIntl } from 'react-intl';
import get from 'lodash/get';
import ActionSheet from 'react-native-actionsheet';
@@ -25,9 +26,10 @@ class ProfileEditScreen extends PureComponent {
// Component Life Cycles
// Component Functions
- _showImageUploadActions = async (action) => {
- await this.setState({ selectedUploadAction: action });
- this.galleryRef.current.show();
+ _showImageUploadActions = (action) => {
+ this.setState({ selectedUploadAction: action }, () => {
+ this.galleryRef.current.show();
+ });
};
render() {
@@ -49,15 +51,19 @@ class ProfileEditScreen extends PureComponent {
avatarUrl,
coverUrl,
isLoading,
+ isUploading,
+ saveEnabled,
handleOnSubmit,
}) => (
+
this._showImageUploadActions('avatarUrl')}
+ isUploading={isUploading && selectedUploadAction === 'avatarUrl'}
/>
this._showImageUploadActions('coverUrl')}
handleOnItemChange={handleOnItemChange}
isLoading={isLoading}
+ isUploading={isUploading && selectedUploadAction === 'coverUrl'}
+ saveEnabled={saveEnabled}
handleOnSubmit={handleOnSubmit}
/>
+