mattermost-mobile/app/screens/edit_profile/components/form.tsx
Avinash Lingaloo dff4f91441
MM-40364 [Gekidou] - Image Picker for Edit Profile (#5901)
* feature edit profile screen

* minor refactoring

* Apply suggestions from code review

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* ts fixes

* revert floatingTextInput label

This reverts commit a778e1f76191aea7c1a18d60a23ffbd6d3dec0eb.

* code clean up

* Apply suggestions from code review

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* code fix

* code fix

* Adding preventDoubleTap

* rename id to fieldKey

* Update edit_profile.tsx

* wip

* navigating through fields; partly done

* navigating through fields - partly done

* navigating through fields; partly done

* completed field navigation

* added theme into dependency array

* code clean up

* revert conditions for disabling fields

* Added colorFilters prop to Loading component

* Completed loading feature on Edit Profile screen

* code clean up

* Add profile_error

* renamed valid_mime_types to  valid_image_mime_types

* added props isDisabled to email field

* refactored next field logic

* fix

* fix

* code clean up

* code clean up

* Updated ESLINT hook rules to warning instead of disabled

* code fix

* code fix

* new line within your_profile component

* added memo for Field component

* added canSave to dependency array

* update loading component - color filter

* Update app/screens/edit_profile/edit_profile.tsx

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* dependency fix

* fix to fetch my latest status

* fix to remove unnecessary user id for local action updateLocalUser

* prevents bouncing for iOS

* code revert

* Adding  textInputStyle and animatedTextStyle to FloatingTextInput component

* correction after dev session

* adding changes as per new ux

* Update edit_profile.tsx

* corrections after ux review

* ux review

* ux review

* code clean up

* Adding userProfileFields into useMemo

* Add enableSaveButton to dependency of submitUser

* Added react-native-image-picker

* fix picker util

* Added action for setDefaultProfileImage

* account outline on remove picture

* Update edit_profile.tsx

* fix image picker

* style fix

* fix image picker

* removed unused types

* mmjstool issue with integrity checksum

* perform camera permission check for Android

* fix to pull latest status

* updated ChangeProfilePicture to EditProfilePciture

* removed integrity key for mmjstool in package-lock.json

* corrections from pr review

* bumping react-native-image-picker to v4.7.1

* pod install

* update to hooks dependency

* fix profile picture component

* added event emitter from edit_profile_picture

* made hitslop a constant

* code clean up

* uploadProfilePicture as a remote action

* else if profileImage removed

* removed check on isBot

* update renderProfilePicture dependencies

* extractFileInfo with try catch

* updated snappoints

* Revert "updated snappoints"

This reverts commit 6d16d480a168755fc80e5bc80569ad3ba561f73b.

* profile picture size

* refactored renderProfilePicture into its own component

* change to if else

* platform select for hasPermissions

* unneeded comment removed

* else if on prefix in edit profile picture

* track has update user info now

* moved image_picker under edit_screen and increased actionSheets item height

* added preventDoubleTap for imagePicker

* multiple uploads

* switch the conditions

* added alert box as requested by Marina

* Revert "added alert box as requested by Marina"

This reverts commit 20735c17a87b40995e05eb4318c138c1adcc6c8c.

* Apply suggestions from code review

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* removed userInfos constant

* added useMemo for certain components on profile_picture

* converting account-outline into a constant

* added panelItem component

* adding return instead of making the function return

* eslint fix

* update i18n  desc

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* hasPictureUrl transferred to file utils

* removing excess mediaType prop

* add USER_PROFILE_PICTURE_SIZE into constant/profile

* relocate hasPictureUrl method

* relocate hasPictureUrl
* rename ImagePicker to ProfileImagePicker

* removing isDestructive property from panelTypes.

* update sectionLimit for attachFileFromPhotoGallery

* Change animation for showModalOverCurrentContext to a quick alpha on iOS

* re-create PickerUtil if intl changes

* Combine styles in edit_profile_picture component

* Split profile image component into smaller components

* useCallback for showFileAttachmentOptions

* split comment into multiple lines

* edit_profile group refs

* remove unnecessary casting

* add new line to file.d.ts

* remove extra space for utils/index.d.ts

* allowMultiSelection for attachFilesFromFiles, default is false

* Split edit profile screen into smaller components

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2022-01-28 16:06:50 +04:00

240 lines
8.2 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useMemo, useRef} from 'react';
import {MessageDescriptor, useIntl} from 'react-intl';
import {Keyboard, StyleSheet, View} from 'react-native';
import {FloatingTextInputRef} from '@app/components/floating_text_input_label';
import {t} from '@app/i18n';
import {useTheme} from '@context/theme';
import DisabledFields from './disabled_fields';
import EmailField from './email_field';
import Field from './field';
import type UserModel from '@typings/database/models/servers/user';
import type {FieldConfig, FieldSequence, UserInfo} from '@typings/screens/edit_profile';
type Props = {
canSave: boolean;
currentUser: UserModel;
isTablet?: boolean;
lockedFirstName: boolean;
lockedLastName: boolean;
lockedNickname: boolean;
lockedPosition: boolean;
onUpdateField: (fieldKey: string, name: string) => void;
userInfo: UserInfo;
submitUser: () => void;
}
const includesSsoService = (sso: string) => ['gitlab', 'google', 'office365'].includes(sso);
const isSAMLOrLDAP = (protocol: string) => ['ldap', 'saml'].includes(protocol);
const FIELDS: { [id: string]: MessageDescriptor } = {
firstName: {
id: t('user.settings.general.firstName'),
defaultMessage: 'First Name',
},
lastName: {
id: t('user.settings.general.lastName'),
defaultMessage: 'Last Name',
},
username: {
id: t('user.settings.general.username'),
defaultMessage: 'Username',
},
nickname: {
id: t('user.settings.general.nickname'),
defaultMessage: 'Nickname',
},
position: {
id: t('user.settings.general.position'),
defaultMessage: 'Position',
},
email: {
id: t('user.settings.general.email'),
defaultMessage: 'Email',
},
};
const styles = StyleSheet.create({
footer: {
height: 40,
width: '100%',
},
separator: {
height: 15,
},
});
const ProfileForm = ({
canSave, currentUser, isTablet,
lockedFirstName, lockedLastName, lockedNickname, lockedPosition,
onUpdateField, userInfo, submitUser,
}: Props) => {
const theme = useTheme();
const {formatMessage} = useIntl();
const firstNameRef = useRef<FloatingTextInputRef>(null);
const lastNameRef = useRef<FloatingTextInputRef>(null);
const usernameRef = useRef<FloatingTextInputRef>(null);
const emailRef = useRef<FloatingTextInputRef>(null);
const nicknameRef = useRef<FloatingTextInputRef>(null);
const positionRef = useRef<FloatingTextInputRef>(null);
const userProfileFields: FieldSequence = useMemo(() => {
const service = currentUser.authService;
return {
firstName: {
ref: firstNameRef,
isDisabled: (isSAMLOrLDAP(service) && lockedFirstName) || includesSsoService(service),
},
lastName: {
ref: lastNameRef,
isDisabled: (isSAMLOrLDAP(service) && lockedLastName) || includesSsoService(service),
},
username: {
ref: usernameRef,
isDisabled: service !== '',
},
email: {
ref: emailRef,
isDisabled: true,
},
nickname: {
ref: nicknameRef,
isDisabled: isSAMLOrLDAP(service) && lockedNickname,
},
position: {
ref: positionRef,
isDisabled: isSAMLOrLDAP(service) && lockedPosition,
},
};
}, [lockedFirstName, lockedLastName, lockedNickname, lockedPosition, currentUser.authService]);
const onFocusNextField = useCallback(((fieldKey: string) => {
const findNextField = () => {
const fields = Object.keys(userProfileFields);
const curIndex = fields.indexOf(fieldKey);
const searchIndex = curIndex + 1;
if (curIndex === -1 || searchIndex > fields.length) {
return undefined;
}
const remainingFields = fields.slice(searchIndex);
const nextFieldIndex = remainingFields.findIndex((f: string) => {
const field = userProfileFields[f];
return !field.isDisabled;
});
if (nextFieldIndex === -1) {
return {isLastEnabledField: true, nextField: undefined};
}
const fieldName = remainingFields[nextFieldIndex];
return {isLastEnabledField: false, nextField: userProfileFields[fieldName]};
};
const next = findNextField();
if (next?.isLastEnabledField && canSave) {
// performs form submission
Keyboard.dismiss();
submitUser();
} else if (next?.nextField) {
next?.nextField?.ref?.current?.focus();
} else {
Keyboard.dismiss();
}
}), [canSave, userProfileFields]);
const hasDisabledFields = Object.values(userProfileFields).filter((field) => field.isDisabled).length > 0;
const fieldConfig: FieldConfig = {
blurOnSubmit: false,
enablesReturnKeyAutomatically: true,
onFocusNextField,
onTextChange: onUpdateField,
returnKeyType: 'next',
};
return (
<>
{hasDisabledFields && <DisabledFields isTablet={isTablet}/>}
<Field
fieldKey='firstName'
fieldRef={firstNameRef}
isDisabled={userProfileFields.firstName.isDisabled}
label={formatMessage(FIELDS.firstName)}
testID='edit_profile.text_setting.firstName'
value={userInfo.firstName}
{...fieldConfig}
/>
<View style={styles.separator}/>
<Field
fieldKey='lastName'
fieldRef={lastNameRef}
isDisabled={userProfileFields.lastName.isDisabled}
label={formatMessage(FIELDS.lastName)}
testID='edit_profile.text_setting.lastName'
value={userInfo.lastName}
{...fieldConfig}
/>
<View style={styles.separator}/>
<Field
fieldKey='username'
fieldRef={usernameRef}
isDisabled={userProfileFields.username.isDisabled}
label={formatMessage(FIELDS.username)}
maxLength={22}
testID='edit_profile.text_setting.username'
value={userInfo.username}
{...fieldConfig}
/>
<View style={styles.separator}/>
{userInfo.email && (
<EmailField
authService={currentUser.authService}
isDisabled={userProfileFields.email.isDisabled}
email={userInfo.email}
label={formatMessage(FIELDS.email)}
fieldRef={emailRef}
onChange={onUpdateField}
onFocusNextField={onFocusNextField}
theme={theme}
isTablet={Boolean(isTablet)}
/>
)}
<View style={styles.separator}/>
<Field
fieldKey='nickname'
fieldRef={nicknameRef}
isDisabled={userProfileFields.nickname.isDisabled}
label={formatMessage(FIELDS.nickname)}
maxLength={22}
testID='edit_profile.text_setting.nickname'
value={userInfo.nickname}
{...fieldConfig}
/>
<View style={styles.separator}/>
<Field
fieldKey='position'
fieldRef={positionRef}
isDisabled={userProfileFields.position.isDisabled}
isOptional={true}
label={formatMessage(FIELDS.position)}
maxLength={128}
{...fieldConfig}
returnKeyType='done'
testID='edit_profile.text_setting.position'
value={userInfo.position}
/>
<View style={styles.footer}/>
</>
);
};
export default ProfileForm;