[MM-46562] Add show/hide password function for login screen (#6612)

This commit is contained in:
Tiago Correia 2022-12-23 14:42:34 +00:00 committed by GitHub
parent 413d3c59dd
commit 97a34fc0e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 87 additions and 44 deletions

View file

@ -5,12 +5,14 @@
import {debounce} from 'lodash';
import React, {useState, useEffect, useRef, useImperativeHandle, forwardRef, useMemo, useCallback} from 'react';
import {GestureResponderEvent, LayoutChangeEvent, NativeSyntheticEvent, Platform, StyleProp, TargetedEvent, Text, TextInput, TextInputFocusEventData, TextInputProps, TextStyle, TouchableWithoutFeedback, View, ViewStyle} from 'react-native';
import {GestureResponderEvent, LayoutChangeEvent, NativeSyntheticEvent, StyleProp, TargetedEvent, Text, TextInput, TextInputFocusEventData, TextInputProps, TextStyle, TouchableWithoutFeedback, View, ViewStyle} from 'react-native';
import Animated, {useAnimatedStyle, withTiming, Easing} from 'react-native-reanimated';
import CompassIcon from '@components/compass_icon';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {getLabelPositions, onExecution} from './utils';
const DEFAULT_INPUT_HEIGHT = 48;
const BORDER_DEFAULT_WIDTH = 1;
const BORDER_FOCUSED_WIDTH = 2;
@ -36,6 +38,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
lineHeight: 16,
paddingVertical: 5,
},
input: {
flex: 1,
},
label: {
position: 'absolute',
color: changeOpacity(theme.centerChannelColor, 0.64),
@ -52,6 +57,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
fontSize: 10,
},
textInput: {
flexDirection: 'row',
fontFamily: 'OpenSans',
fontSize: 16,
paddingTop: 12,
@ -65,29 +71,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
},
}));
const onExecution = (
e: NativeSyntheticEvent<TextInputFocusEventData>,
innerFunc?: () => void,
outerFunc?: ((event: NativeSyntheticEvent<TargetedEvent>) => void),
) => {
innerFunc?.();
outerFunc?.(e);
};
const getLabelPositions = (style: TextStyle, labelStyle: TextStyle, smallLabelStyle: TextStyle) => {
const top: number = style.paddingTop as number || 0;
const bottom: number = style.paddingBottom as number || 0;
const height: number = (style.height as number || (top + bottom) || style.padding as number) || 0;
const textInputFontSize = style.fontSize || 13;
const labelFontSize = labelStyle.fontSize || 16;
const smallLabelFontSize = smallLabelStyle.fontSize || 10;
const fontSizeDiff = textInputFontSize - labelFontSize;
const unfocused = (height * 0.5) + (fontSizeDiff * (Platform.OS === 'android' ? 0.5 : 0.6));
const focused = -(labelFontSize + smallLabelFontSize) * 0.25;
return [unfocused, focused];
};
export type FloatingTextInputRef = {
blur: () => void;
focus: () => void;
@ -97,6 +80,7 @@ export type FloatingTextInputRef = {
type FloatingTextInputProps = TextInputProps & {
containerStyle?: ViewStyle;
editable?: boolean;
endAdornment?: React.ReactNode;
error?: string;
errorIcon?: string;
isKeyboardInput?: boolean;
@ -120,6 +104,7 @@ const FloatingTextInput = forwardRef<FloatingTextInputRef, FloatingTextInputProp
editable = true,
error,
errorIcon = 'alert-outline',
endAdornment,
isKeyboardInput = true,
label = '',
labelTextStyle,
@ -192,7 +177,7 @@ const FloatingTextInput = forwardRef<FloatingTextInputRef, FloatingTextInputProp
return res;
}, [styles, containerStyle, multiline]);
const combinedTextInputStyle = useMemo(() => {
const combinedTextInputContainerStyle = useMemo(() => {
const res: StyleProp<TextStyle> = [styles.textInput];
if (!editable) {
res.push(styles.readOnly);
@ -253,21 +238,24 @@ const FloatingTextInput = forwardRef<FloatingTextInputRef, FloatingTextInputProp
>
{label}
</Animated.Text>
<TextInput
{...props}
editable={isKeyboardInput && editable}
style={combinedTextInputStyle}
placeholder={placeholder}
placeholderTextColor={styles.label.color}
multiline={multiline}
value={value}
pointerEvents={isKeyboardInput ? 'auto' : 'none'}
onFocus={onTextInputFocus}
onBlur={onTextInputBlur}
ref={inputRef}
underlineColorAndroid='transparent'
testID={testID}
/>
<View style={combinedTextInputContainerStyle}>
<TextInput
{...props}
editable={isKeyboardInput && editable}
style={styles.input}
placeholder={placeholder}
placeholderTextColor={styles.label.color}
multiline={multiline}
value={value}
pointerEvents={isKeyboardInput ? 'auto' : 'none'}
onFocus={onTextInputFocus}
onBlur={onTextInputBlur}
ref={inputRef}
underlineColorAndroid='transparent'
testID={testID}
/>
{endAdornment}
</View>
{Boolean(error) && (
<View style={styles.errorContainer}>
{showErrorIcon && errorIcon &&

View file

@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {NativeSyntheticEvent, Platform, TargetedEvent, TextInputFocusEventData, TextStyle} from 'react-native';
export const onExecution = (
e: NativeSyntheticEvent<TextInputFocusEventData>,
innerFunc?: () => void,
outerFunc?: ((event: NativeSyntheticEvent<TargetedEvent>) => void),
) => {
innerFunc?.();
outerFunc?.(e);
};
export const getLabelPositions = (style: TextStyle, labelStyle: TextStyle, smallLabelStyle: TextStyle) => {
const top: number = style.paddingTop as number || 0;
const bottom: number = style.paddingBottom as number || 0;
const height: number = (style.height as number || (top + bottom) || style.padding as number) || 0;
const textInputFontSize = style.fontSize || 13;
const labelFontSize = labelStyle.fontSize || 16;
const smallLabelFontSize = smallLabelStyle.fontSize || 10;
const fontSizeDiff = textInputFontSize - labelFontSize;
const unfocused = (height * 0.5) + (fontSizeDiff * (Platform.OS === 'android' ? 0.5 : 0.6));
const focused = -(labelFontSize + smallLabelFontSize) * 0.25;
return [unfocused, focused];
};

View file

@ -4,11 +4,12 @@
import {useManagedConfig} from '@mattermost/react-native-emm';
import React, {MutableRefObject, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {Keyboard, Platform, TextInput, useWindowDimensions, View} from 'react-native';
import {Keyboard, Platform, TextInput, TouchableOpacity, useWindowDimensions, View} from 'react-native';
import Button from 'react-native-button';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import {login} from '@actions/remote/session';
import CompassIcon from '@app/components/compass_icon';
import ClientError from '@client/rest/error';
import FloatingTextInput from '@components/floating_text_input_label';
import FormattedText from '@components/formatted_text';
@ -20,7 +21,7 @@ import {goToScreen, loginAnimationOptions, resetToHome, resetToTeams} from '@scr
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {isServerError} from '@utils/errors';
import {preventDoubleTap} from '@utils/tap';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import type {LaunchProps} from '@typings/launch';
@ -34,6 +35,7 @@ interface LoginProps extends LaunchProps {
}
export const MFA_EXPECTED_ERRORS = ['mfa.validate_token.authenticate.app_error', 'ent.mfa.validate_token.authenticate.app_error'];
const hitSlop = {top: 8, right: 8, bottom: 8, left: 8};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
@ -69,6 +71,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
loginButton: {
marginTop: 25,
},
endAdornment: {
top: 2,
},
}));
const LoginForm = ({config, extra, keyboardAwareRef, numberSSOs, serverDisplayName, launchError, launchType, license, serverUrl, theme}: LoginProps) => {
@ -84,6 +89,7 @@ const LoginForm = ({config, extra, keyboardAwareRef, numberSSOs, serverDisplayNa
const [loginId, setLoginId] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [buttonDisabled, setButtonDisabled] = useState(true);
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const emailEnabled = config.EnableSignInWithEmail === 'true';
const usernameEnabled = config.EnableSignInWithUsername === 'true';
const ldapEnabled = license.IsLicensed === 'true' && config.EnableLdap === 'true' && license.LDAP === 'true';
@ -266,6 +272,10 @@ const LoginForm = ({config, extra, keyboardAwareRef, numberSSOs, serverDisplayNa
goToScreen(FORGOT_PASSWORD, '', passProps, loginAnimationOptions());
}, [theme]);
const togglePasswordVisiblity = useCallback(() => {
setIsPasswordVisible((prevState) => !prevState);
}, []);
// useEffect to set userName for EMM
useEffect(() => {
const setEmmUsernameIfAvailable = async () => {
@ -324,6 +334,20 @@ const LoginForm = ({config, extra, keyboardAwareRef, numberSSOs, serverDisplayNa
);
}, [buttonDisabled, loginId, password, isLoading, theme]);
const endAdornment = (
<TouchableOpacity
onPress={togglePasswordVisiblity}
hitSlop={hitSlop}
style={styles.endAdornment}
>
<CompassIcon
name={isPasswordVisible ? 'eye-off-outline' : 'eye-outline'}
size={20}
color={changeOpacity(theme.centerChannelColor, 0.64)}
/>
</TouchableOpacity>
);
return (
<View style={styles.container}>
<FloatingTextInput
@ -365,10 +389,11 @@ const LoginForm = ({config, extra, keyboardAwareRef, numberSSOs, serverDisplayNa
ref={passwordRef}
returnKeyType='join'
spellCheck={false}
secureTextEntry={true}
secureTextEntry={!isPasswordVisible}
testID='login_form.password.input'
theme={theme}
value={password}
endAdornment={endAdornment}
/>
{(emailEnabled || usernameEnabled) && (

View file

@ -42,6 +42,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
alignSelf: 'center',
paddingHorizontal: 18.5,
},
labelTextStyle: {left: 32},
keywordLabelStyle: {
paddingHorizontal: 18.5,
marginTop: 4,
@ -201,7 +202,7 @@ const MentionSettings = ({componentId, currentUser, isCRTEnabled}: MentionSectio
blurOnSubmit={true}
containerStyle={styles.containerStyle}
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
label={intl.formatMessage({id: 'notification_settings.mentions.keywords', defaultMessage: 'Enter other keywords'})}
label={intl.formatMessage({id: 'notification_settings.mentions.keywords', defaultMessage: 'Keywords'})}
multiline={true}
onChangeText={onChangeText}
placeholder={intl.formatMessage({id: 'notification_settings.mentions..keywordsDescription', defaultMessage: 'Other words that trigger a mention'})}
@ -213,6 +214,7 @@ const MentionSettings = ({componentId, currentUser, isCRTEnabled}: MentionSectio
theme={theme}
underlineColorAndroid='transparent'
value={mentionKeywords}
labelTextStyle={styles.labelTextStyle}
/>
<Text
style={styles.keywordLabelStyle}