[MM-46562] Add show/hide password function for login screen (#6612)
This commit is contained in:
parent
413d3c59dd
commit
97a34fc0e0
4 changed files with 87 additions and 44 deletions
|
|
@ -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 &&
|
||||
|
|
|
|||
28
app/components/floating_text_input_label/utils.ts
Normal file
28
app/components/floating_text_input_label/utils.ts
Normal 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];
|
||||
};
|
||||
|
||||
|
|
@ -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) && (
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in a new issue