add: `AvatarEditView`

This commit is contained in:
Gerzon Z 2022-05-05 13:51:14 -04:00
parent 305f360b40
commit 98bdd552cd
12 changed files with 130 additions and 88 deletions

View File

@ -1,4 +1,4 @@
import { IParams } from '../../IProfileViewInterfaces'; import { IParams } from '../../../views/ProfileView/interfaces';
import type { ITeam } from '../../ITeam'; import type { ITeam } from '../../ITeam';
import type { IUser } from '../../IUser'; import type { IUser } from '../../IUser';
import { INotificationPreferences, IUserPreferences, IUserRegistered } from '../../IUser'; import { INotificationPreferences, IUserPreferences, IUserRegistered } from '../../IUser';

View File

@ -114,6 +114,7 @@
"Auto_Translate": "Auto-Translate", "Auto_Translate": "Auto-Translate",
"Avatar_changed_successfully": "Avatar changed successfully!", "Avatar_changed_successfully": "Avatar changed successfully!",
"Avatar_Url": "Avatar URL", "Avatar_Url": "Avatar URL",
"Avatar_Url_Insert_Image": "Insert image URL here",
"Away": "Away", "Away": "Away",
"Back": "Back", "Back": "Back",
"Black": "Black", "Black": "Black",

View File

@ -7,7 +7,7 @@ import {
SubscriptionType, SubscriptionType,
IUser IUser
} from '../../definitions'; } from '../../definitions';
import { IAvatarSuggestion, IParams } from '../../definitions/IProfileViewInterfaces'; import { IAvatarSuggestion, IParams } from '../../views/ProfileView/interfaces';
import { ISpotlight } from '../../definitions/ISpotlight'; import { ISpotlight } from '../../definitions/ISpotlight';
import { TEAM_TYPE } from '../../definitions/ITeam'; import { TEAM_TYPE } from '../../definitions/ITeam';
import { Encryption } from '../encryption'; import { Encryption } from '../encryption';

View File

@ -33,6 +33,7 @@ import CannedResponseDetail from '../views/CannedResponseDetail';
import { themes } from '../lib/constants'; import { themes } from '../lib/constants';
// Profile Stack // Profile Stack
import ProfileView from '../views/ProfileView'; import ProfileView from '../views/ProfileView';
import AvatarEditView from '../views/AvatarEditView';
import UserPreferencesView from '../views/UserPreferencesView'; import UserPreferencesView from '../views/UserPreferencesView';
import UserNotificationPrefView from '../views/UserNotificationPreferencesView'; import UserNotificationPrefView from '../views/UserNotificationPreferencesView';
// Display Preferences View // Display Preferences View
@ -148,6 +149,7 @@ const ProfileStackNavigator = () => {
<ProfileStack.Navigator <ProfileStack.Navigator
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions}> screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions}>
<ProfileStack.Screen name='ProfileView' component={ProfileView} options={ProfileView.navigationOptions} /> <ProfileStack.Screen name='ProfileView' component={ProfileView} options={ProfileView.navigationOptions} />
<ProfileStack.Screen name='AvatarEditView' component={AvatarEditView} />
<ProfileStack.Screen name='UserPreferencesView' component={UserPreferencesView} /> <ProfileStack.Screen name='UserPreferencesView' component={UserPreferencesView} />
<ProfileStack.Screen <ProfileStack.Screen
name='UserNotificationPrefView' name='UserNotificationPrefView'

View File

@ -44,6 +44,7 @@ import UserNotificationPrefView from '../../views/UserNotificationPreferencesVie
import SecurityPrivacyView from '../../views/SecurityPrivacyView'; import SecurityPrivacyView from '../../views/SecurityPrivacyView';
import E2EEncryptionSecurityView from '../../views/E2EEncryptionSecurityView'; import E2EEncryptionSecurityView from '../../views/E2EEncryptionSecurityView';
// InsideStackNavigator // InsideStackNavigator
import AvatarEditView from '../../views/AvatarEditView';
import AttachmentView from '../../views/AttachmentView'; import AttachmentView from '../../views/AttachmentView';
import ModalBlockView from '../../views/ModalBlockView'; import ModalBlockView from '../../views/ModalBlockView';
import JitsiMeetView from '../../views/JitsiMeetView'; import JitsiMeetView from '../../views/JitsiMeetView';
@ -195,6 +196,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
component={ProfileView} component={ProfileView}
options={props => ProfileView.navigationOptions!({ ...props, isMasterDetail: true })} options={props => ProfileView.navigationOptions!({ ...props, isMasterDetail: true })}
/> />
<ModalStack.Screen name='AvatarEditView' component={AvatarEditView} />
<ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} /> <ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} />
<ModalStack.Screen <ModalStack.Screen
name='AdminPanelView' name='AdminPanelView'

View File

@ -150,6 +150,7 @@ export type ModalStackParamList = {
ScreenLockConfigView: undefined; ScreenLockConfigView: undefined;
StatusView: undefined; StatusView: undefined;
ProfileView: undefined; ProfileView: undefined;
AvatarEditView: undefined;
DisplayPrefsView: undefined; DisplayPrefsView: undefined;
AdminPanelView: undefined; AdminPanelView: undefined;
NewMessageView: undefined; NewMessageView: undefined;

View File

@ -157,6 +157,7 @@ export type ChatsStackParamList = {
export type ProfileStackParamList = { export type ProfileStackParamList = {
ProfileView: undefined; ProfileView: undefined;
AvatarEditView: undefined;
UserPreferencesView: undefined; UserPreferencesView: undefined;
UserNotificationPrefView: undefined; UserNotificationPrefView: undefined;
PickerView: { PickerView: {

View File

@ -0,0 +1,53 @@
import React, { useLayoutEffect, useState } from 'react';
// import { useSelector } from 'react-redux';
import { ProfileStackParamList } from '../../stacks/types';
import { IBaseScreen } from '../../definitions';
import SafeAreaView from '../../containers/SafeAreaView';
import Button from '../../containers/Button';
import { useTheme } from '../../theme';
import i18n from '../../i18n';
import RCTextInput from '../../containers/TextInput';
// import { getUserSelector } from '../../selectors/login';
// import Avatar from '../../containers/Avatar';
const AvatarEditView = ({ navigation }: IBaseScreen<ProfileStackParamList, 'AvatarEditView'>): React.ReactElement => {
const [avatarUrl, setAvatarUrl] = useState('');
const [saving] = useState(false);
// const username = useSelector((state: IApplicationState) => getUserSelector(state).username);
// const url = useSelector((state: IApplicationState) => getUserSelector(state).username);
const { theme } = useTheme();
useLayoutEffect(() => {
navigation.setOptions({
title: 'Avatar'
});
}, []);
return (
<SafeAreaView>
{/* <Avatar text={username} avatar={avatar?.url} isStatic={avatar?.url} size={120} /> */}
<RCTextInput
label={i18n.t('Avatar_Url')}
placeholder={i18n.t('Avatar_Url_Insert_Image')}
value={avatarUrl}
onChangeText={value => setAvatarUrl(value)}
// onSubmitEditing={this.submit}
testID='profile-view-avatar-url'
theme={theme}
/>
<Button
title={i18n.t('Save_Changes')}
type='primary'
// onPress={this.submit}
// disabled={!this.formIsChanged()}
testID='profile-view-submit'
loading={saving}
theme={theme}
/>
</SafeAreaView>
);
};
export default AvatarEditView;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Keyboard, ScrollView, View } from 'react-native'; import { Keyboard, ScrollView, Text, View } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import prompt from 'react-native-prompt-android'; import prompt from 'react-native-prompt-android';
import { sha256 } from 'js-sha256'; import { sha256 } from 'js-sha256';
@ -22,7 +22,6 @@ import I18n from '../../i18n';
import Button from '../../containers/Button'; import Button from '../../containers/Button';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
import { setUser as setUserAction } from '../../actions/login'; import { setUser as setUserAction } from '../../actions/login';
import { CustomIcon } from '../../containers/CustomIcon';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import { themes } from '../../lib/constants'; import { themes } from '../../lib/constants';
@ -30,14 +29,7 @@ import { withTheme } from '../../theme';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import styles from './styles'; import styles from './styles';
import { import { IAvatar, IAvatarButton, IParams, IProfileViewProps, IProfileViewState } from './interfaces';
IAvatar,
IAvatarButton,
INavigationOptions,
IParams,
IProfileViewProps,
IProfileViewState
} from '../../definitions/IProfileViewInterfaces';
import { IUser } from '../../definitions'; import { IUser } from '../../definitions';
import { Services } from '../../lib/services'; import { Services } from '../../lib/services';
@ -48,7 +40,7 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
private avatarUrl: any; private avatarUrl: any;
private newPassword: any; private newPassword: any;
static navigationOptions = ({ navigation, isMasterDetail }: INavigationOptions) => { static navigationOptions = ({ navigation, isMasterDetail }: IProfileViewProps) => {
const options: StackNavigationOptions = { const options: StackNavigationOptions = {
title: I18n.t('Profile') title: I18n.t('Profile')
}; };
@ -319,48 +311,48 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
); );
}; };
renderAvatarButtons = () => { // renderAvatarButtons = () => {
const { avatarUrl, avatarSuggestions } = this.state; // const { avatarUrl, avatarSuggestions } = this.state;
const { user, theme, Accounts_AllowUserAvatarChange } = this.props; // const { user, theme, Accounts_AllowUserAvatarChange } = this.props;
return ( // return (
<View style={styles.avatarButtons}> // <View style={styles.avatarButtons}>
{this.renderAvatarButton({ // {this.renderAvatarButton({
child: <Avatar text={`@${user.username}`} size={50} />, // child: <Avatar text={`@${user.username}`} size={50} />,
onPress: () => this.resetAvatar(), // onPress: () => this.resetAvatar(),
disabled: !Accounts_AllowUserAvatarChange, // disabled: !Accounts_AllowUserAvatarChange,
key: 'profile-view-reset-avatar' // key: 'profile-view-reset-avatar'
})} // })}
{this.renderAvatarButton({ // {this.renderAvatarButton({
child: <CustomIcon name='upload' size={30} color={themes[theme].bodyText} />, // child: <CustomIcon name='upload' size={30} color={themes[theme].bodyText} />,
onPress: () => this.pickImage(), // onPress: () => this.pickImage(),
disabled: !Accounts_AllowUserAvatarChange, // disabled: !Accounts_AllowUserAvatarChange,
key: 'profile-view-upload-avatar' // key: 'profile-view-upload-avatar'
})} // })}
{this.renderAvatarButton({ // {this.renderAvatarButton({
child: <CustomIcon name='link' size={30} color={themes[theme].bodyText} />, // child: <CustomIcon name='link' size={30} color={themes[theme].bodyText} />,
onPress: () => this.pickImageWithURL(avatarUrl!), // onPress: () => this.pickImageWithURL(avatarUrl!),
disabled: !avatarUrl, // disabled: !avatarUrl,
key: 'profile-view-avatar-url-button' // key: 'profile-view-avatar-url-button'
})} // })}
{Object.keys(avatarSuggestions).map(service => { // {Object.keys(avatarSuggestions).map(service => {
const { url, blob, contentType } = avatarSuggestions[service]; // const { url, blob, contentType } = avatarSuggestions[service];
return this.renderAvatarButton({ // return this.renderAvatarButton({
disabled: !Accounts_AllowUserAvatarChange, // disabled: !Accounts_AllowUserAvatarChange,
key: `profile-view-avatar-${service}`, // key: `profile-view-avatar-${service}`,
child: <Avatar avatar={url} size={50} />, // child: <Avatar avatar={url} size={50} />,
onPress: () => // onPress: () =>
this.setAvatar({ // this.setAvatar({
url, // url,
data: blob, // data: blob,
service, // service,
contentType // contentType
}) // })
}); // });
})} // })}
</View> // </View>
); // );
}; // };
renderCustomFields = () => { renderCustomFields = () => {
const { customFields } = this.state; const { customFields } = this.state;
@ -448,7 +440,7 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
}; };
render() { render() {
const { name, username, email, newPassword, avatarUrl, customFields, avatar, saving } = this.state; const { name, username, email, newPassword, customFields, avatar, saving } = this.state;
const { const {
user, user,
theme, theme,
@ -457,7 +449,8 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
Accounts_AllowRealNameChange, Accounts_AllowRealNameChange,
Accounts_AllowUserAvatarChange, Accounts_AllowUserAvatarChange,
Accounts_AllowUsernameChange, Accounts_AllowUsernameChange,
Accounts_CustomFields Accounts_CustomFields,
navigation
} = this.props; } = this.props;
return ( return (
@ -469,7 +462,17 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
<SafeAreaView testID='profile-view'> <SafeAreaView testID='profile-view'>
<ScrollView contentContainerStyle={sharedStyles.containerScrollView} testID='profile-view-list' {...scrollPersistTaps}> <ScrollView contentContainerStyle={sharedStyles.containerScrollView} testID='profile-view-list' {...scrollPersistTaps}>
<View style={styles.avatarContainer} testID='profile-view-avatar'> <View style={styles.avatarContainer} testID='profile-view-avatar'>
<Avatar text={user.username} avatar={avatar?.url} isStatic={avatar?.url} size={100} /> <Avatar text={user.username} avatar={avatar?.url} isStatic={avatar?.url} size={120} />
{Accounts_AllowUserAvatarChange ? (
<Touch
key={'profile-view-edit-avatar'}
testID={'profile-view-edit-avatar'}
onPress={() => navigation.navigate('AvatarEditView')}
style={[styles.avatarButton, { backgroundColor: themes[theme].borderColor }]}
theme={theme}>
<Text style={{ color: themes[theme].bodyText }}>Edit</Text>
</Touch>
) : null}
</View> </View>
<RCTextInput <RCTextInput
editable={Accounts_AllowRealNameChange} editable={Accounts_AllowRealNameChange}
@ -534,28 +537,13 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
// @ts-ignore // @ts-ignore
return this[Object.keys(customFields)[0]].focus(); return this[Object.keys(customFields)[0]].focus();
} }
this.avatarUrl.focus(); this.submit();
}} }}
secureTextEntry secureTextEntry
testID='profile-view-new-password' testID='profile-view-new-password'
theme={theme} theme={theme}
/> />
{this.renderCustomFields()} {this.renderCustomFields()}
<RCTextInput
editable={Accounts_AllowUserAvatarChange}
inputStyle={[!Accounts_AllowUserAvatarChange && styles.disabled]}
inputRef={e => {
this.avatarUrl = e;
}}
label={I18n.t('Avatar_Url')}
placeholder={I18n.t('Avatar_Url')}
value={avatarUrl!}
onChangeText={value => this.setState({ avatarUrl: value })}
onSubmitEditing={this.submit}
testID='profile-view-avatar-url'
theme={theme}
/>
{this.renderAvatarButtons()}
<Button <Button
title={I18n.t('Save_Changes')} title={I18n.t('Save_Changes')}
type='primary' type='primary'

View File

@ -1,9 +1,8 @@
import { StackNavigationProp } from '@react-navigation/stack';
import React from 'react'; import React from 'react';
import { TSupportedThemes } from '../theme'; import { IBaseScreen, IUser } from '../../definitions';
import { ProfileStackParamList } from '../stacks/types'; import { TSupportedThemes } from '../../theme';
import { IUser } from './IUser'; import { ProfileStackParamList } from '../../stacks/types';
export interface IParams { export interface IParams {
name: string; name: string;
@ -20,12 +19,7 @@ export interface IAvatarButton {
disabled: boolean; disabled: boolean;
} }
export interface INavigationOptions { export interface IProfileViewProps extends IBaseScreen<ProfileStackParamList, 'ProfileView'> {
navigation: StackNavigationProp<ProfileStackParamList, 'ProfileView'>;
isMasterDetail?: boolean;
}
export interface IProfileViewProps {
user: IUser; user: IUser;
baseUrl: string; baseUrl: string;
Accounts_AllowEmailChange: boolean; Accounts_AllowEmailChange: boolean;

View File

@ -16,12 +16,12 @@ export default StyleSheet.create({
}, },
avatarButton: { avatarButton: {
backgroundColor: '#e1e5e8', backgroundColor: '#e1e5e8',
width: 50, width: 48,
height: 50, height: 32,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
marginRight: 15, marginTop: 16,
marginBottom: 15, marginBottom: 24,
borderRadius: 2 borderRadius: 2
} }
}); });

View File

@ -32,7 +32,7 @@ import log, { events, logEvent } from '../../utils/log';
import { MessageTypeValues } from '../../utils/messageTypes'; import { MessageTypeValues } from '../../utils/messageTypes';
import random from '../../utils/random'; import random from '../../utils/random';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { IAvatar } from '../../definitions/IProfileViewInterfaces'; import { IAvatar } from '../ProfileView/interfaces';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
import styles from './styles'; import styles from './styles';
import SwitchContainer from './SwitchContainer'; import SwitchContainer from './SwitchContainer';