fix screen position for server / login / forgot password & mfa (#8340)
* fix screen position for server / login / forgot password & mfa * refactor to use a hook * fix scroll to offset not to height * feedback review
This commit is contained in:
parent
7d8d4a4c68
commit
37fd1609c7
8 changed files with 48 additions and 100 deletions
|
|
@ -9,6 +9,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|||
import {DeviceContext} from '@context/device';
|
||||
|
||||
import type {KeyboardTrackingViewRef, KeyboardWillShowEventData} from '@mattermost/keyboard-tracker';
|
||||
import type {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||
|
||||
const utilsEmitter = new NativeEventEmitter(RNUtils);
|
||||
|
||||
|
|
@ -147,3 +148,16 @@ export function useKeyboardOverlap(viewRef: RefObject<View>, containerHeight: nu
|
|||
|
||||
return overlap;
|
||||
}
|
||||
|
||||
export function useAvoidKeyboard(ref: RefObject<KeyboardAwareScrollView>, dimisher = 3) {
|
||||
const height = useKeyboardHeight();
|
||||
|
||||
useEffect(() => {
|
||||
let offsetY = height / dimisher;
|
||||
if (offsetY < 80) {
|
||||
offsetY = 0;
|
||||
}
|
||||
|
||||
ref.current?.scrollToPosition(0, offsetY);
|
||||
}, [height, dimisher, ref]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import FloatingTextInput from '@components/floating_text_input_label';
|
|||
import FormattedText from '@components/formatted_text';
|
||||
import {Screens} from '@constants';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {useAvoidKeyboard} from '@hooks/device';
|
||||
import Background from '@screens/background';
|
||||
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
||||
import {isEmail} from '@utils/helpers';
|
||||
|
|
@ -93,7 +93,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
|
||||
const dimensions = useWindowDimensions();
|
||||
const translateX = useSharedValue(dimensions.width);
|
||||
const isTablet = useIsTablet();
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [error, setError] = useState<string>('');
|
||||
const [isPasswordLinkSent, setIsPasswordLinkSent] = useState<boolean>(false);
|
||||
|
|
@ -101,25 +100,13 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
|
|||
const keyboardAwareRef = useRef<KeyboardAwareScrollView>(null);
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
useAvoidKeyboard(keyboardAwareRef);
|
||||
|
||||
const changeEmail = useCallback((emailAddress: string) => {
|
||||
setEmail(emailAddress);
|
||||
setError('');
|
||||
}, []);
|
||||
|
||||
const onFocus = useCallback(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
let offsetY = 150;
|
||||
if (isTablet) {
|
||||
const {width, height} = dimensions;
|
||||
const isLandscape = width > height;
|
||||
offsetY = (isLandscape ? 230 : 150);
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
keyboardAwareRef.current?.scrollToPosition(0, offsetY);
|
||||
});
|
||||
}
|
||||
}, [dimensions]);
|
||||
|
||||
const onReturn = useCallback(() => {
|
||||
Navigation.popTo(Screens.LOGIN);
|
||||
}, []);
|
||||
|
|
@ -146,7 +133,7 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
|
|||
id: 'password_send.generic_error',
|
||||
defaultMessage: 'We were unable to send you a reset password link. Please contact your System Admin for assistance.',
|
||||
}));
|
||||
}, [email]);
|
||||
}, [email, formatMessage, serverUrl]);
|
||||
|
||||
const getCenterContent = () => {
|
||||
if (isPasswordLinkSent) {
|
||||
|
|
@ -188,7 +175,7 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
|
|||
<KeyboardAwareScrollView
|
||||
bounces={false}
|
||||
contentContainerStyle={styles.innerContainer}
|
||||
enableAutomaticScroll={Platform.OS === 'android'}
|
||||
enableAutomaticScroll={false}
|
||||
enableOnAndroid={false}
|
||||
enableResetScrollToCoords={true}
|
||||
extraScrollHeight={0}
|
||||
|
|
@ -224,7 +211,6 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
|
|||
keyboardType='email-address'
|
||||
label={formatMessage({id: 'login.email', defaultMessage: 'Email'})}
|
||||
onChangeText={changeEmail}
|
||||
onFocus={onFocus}
|
||||
onSubmitEditing={submitResetPassword}
|
||||
returnKeyType='next'
|
||||
spellCheck={false}
|
||||
|
|
@ -270,7 +256,7 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
|
|||
const unsubscribe = Navigation.events().registerComponentListener(listener, componentId);
|
||||
|
||||
return () => unsubscribe.remove();
|
||||
}, [dimensions]);
|
||||
}, [componentId, dimensions]);
|
||||
|
||||
useEffect(() => {
|
||||
translateX.value = 0;
|
||||
|
|
|
|||
|
|
@ -295,6 +295,6 @@ export function registerScreens() {
|
|||
const serverScreen = require('@screens/server').default;
|
||||
const onboardingScreen = require('@screens/onboarding').default;
|
||||
Navigation.registerComponent(Screens.ONBOARDING, () => withGestures(withIntl(withManagedConfig(onboardingScreen)), undefined));
|
||||
Navigation.registerComponent(Screens.SERVER, () => withGestures(withIntl(withManagedConfig(serverScreen)), undefined));
|
||||
Navigation.registerComponent(Screens.SERVER, () => withSafeAreaInsets(withGestures(withIntl(withManagedConfig(serverScreen)), undefined)));
|
||||
Navigation.registerComponent(Screens.HOME, () => withGestures(withSafeAreaInsets(withServerDatabase(withManagedConfig(homeScreen))), undefined));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import {Button} from '@rneui/base';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState, type RefObject} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, TextInput, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
|
|
@ -13,6 +13,7 @@ import FloatingTextInput from '@components/floating_text_input_label';
|
|||
import FormattedText from '@components/formatted_text';
|
||||
import Loading from '@components/loading';
|
||||
import {FORGOT_PASSWORD, MFA} from '@constants/screens';
|
||||
import {useAvoidKeyboard} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {goToScreen, loginAnimationOptions, resetToHome} from '@screens/navigation';
|
||||
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
||||
|
|
@ -22,10 +23,12 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|||
import {tryOpenURL} from '@utils/url';
|
||||
|
||||
import type {LaunchProps} from '@typings/launch';
|
||||
import type {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||
|
||||
interface LoginProps extends LaunchProps {
|
||||
config: Partial<ClientConfig>;
|
||||
license: Partial<ClientLicense>;
|
||||
keyboardAwareRef: RefObject<KeyboardAwareScrollView>;
|
||||
serverDisplayName: string;
|
||||
theme: Theme;
|
||||
}
|
||||
|
|
@ -77,7 +80,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const LoginForm = ({config, extra, serverDisplayName, launchError, launchType, license, serverUrl, theme}: LoginProps) => {
|
||||
const LoginForm = ({config, extra, keyboardAwareRef, serverDisplayName, launchError, launchType, license, serverUrl, theme}: LoginProps) => {
|
||||
const styles = getStyleSheet(theme);
|
||||
const loginRef = useRef<TextInput>(null);
|
||||
const passwordRef = useRef<TextInput>(null);
|
||||
|
|
@ -93,6 +96,8 @@ const LoginForm = ({config, extra, serverDisplayName, launchError, launchType, l
|
|||
const usernameEnabled = config.EnableSignInWithUsername === 'true';
|
||||
const ldapEnabled = license.IsLicensed === 'true' && config.EnableLdap === 'true' && license.LDAP === 'true';
|
||||
|
||||
useAvoidKeyboard(keyboardAwareRef);
|
||||
|
||||
const preSignIn = preventDoubleTap(async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
|
|
@ -222,7 +227,7 @@ const LoginForm = ({config, extra, serverDisplayName, launchError, launchType, l
|
|||
};
|
||||
|
||||
goToScreen(FORGOT_PASSWORD, '', passProps, loginAnimationOptions());
|
||||
}, [theme]);
|
||||
}, [config.ForgotPasswordLink, serverUrl, theme]);
|
||||
|
||||
const togglePasswordVisiblity = useCallback(() => {
|
||||
setIsPasswordVisible((prevState) => !prevState);
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ const LoginOptions = ({
|
|||
defaultMessage="You can't log in to your account yet. At least one login option must be configured. Contact your System Admin for assistance."
|
||||
/>
|
||||
);
|
||||
}, [hasLoginForm, numberSSOs, theme]);
|
||||
}, [hasLoginForm, numberSSOs, styles.subheader]);
|
||||
|
||||
const goToSso = preventDoubleTap((ssoType: string) => {
|
||||
goToScreen(Screens.SSO, '', {config, extra, launchError, launchType, license, theme, ssoType, serverDisplayName, serverUrl}, loginAnimationOptions());
|
||||
|
|
@ -160,7 +160,7 @@ const LoginOptions = ({
|
|||
});
|
||||
|
||||
return () => navigationEvents.remove();
|
||||
}, []);
|
||||
}, [closeButtonId, componentId, serverUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
translateX.value = 0;
|
||||
|
|
@ -178,7 +178,7 @@ const LoginOptions = ({
|
|||
const unsubscribe = Navigation.events().registerComponentListener(listener, Screens.LOGIN);
|
||||
|
||||
return () => unsubscribe.remove();
|
||||
}, [dimensions]);
|
||||
}, [dimensions, translateX]);
|
||||
|
||||
useNavButtonPressed(closeButtonId || '', componentId, dismiss, []);
|
||||
useAndroidHardwareBackHandler(componentId, pop);
|
||||
|
|
@ -219,11 +219,11 @@ const LoginOptions = ({
|
|||
<KeyboardAwareScrollView
|
||||
bounces={true}
|
||||
contentContainerStyle={[styles.innerContainer, additionalContainerStyle]}
|
||||
enableAutomaticScroll={true}
|
||||
enableAutomaticScroll={false}
|
||||
enableOnAndroid={false}
|
||||
enableResetScrollToCoords={true}
|
||||
extraScrollHeight={0}
|
||||
keyboardDismissMode='interactive'
|
||||
extraScrollHeight={20}
|
||||
keyboardDismissMode='on-drag'
|
||||
keyboardShouldPersistTaps='handled'
|
||||
ref={keyboardAwareRef}
|
||||
scrollToOverflowEnabled={true}
|
||||
|
|
@ -239,6 +239,7 @@ const LoginOptions = ({
|
|||
<Form
|
||||
config={config}
|
||||
extra={extra}
|
||||
keyboardAwareRef={keyboardAwareRef}
|
||||
license={license}
|
||||
launchError={launchError}
|
||||
launchType={launchType}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import FloatingTextInput from '@components/floating_text_input_label';
|
|||
import FormattedText from '@components/formatted_text';
|
||||
import Loading from '@components/loading';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {useAvoidKeyboard} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import Background from '@screens/background';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
|
|
@ -99,7 +99,6 @@ const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView);
|
|||
const MFA = ({componentId, config, goToHome, license, loginId, password, serverDisplayName, serverUrl, theme}: MFAProps) => {
|
||||
const dimensions = useWindowDimensions();
|
||||
const translateX = useSharedValue(dimensions.width);
|
||||
const isTablet = useIsTablet();
|
||||
const keyboardAwareRef = useRef<KeyboardAwareScrollView>(null);
|
||||
const intl = useIntl();
|
||||
const [token, setToken] = useState<string>('');
|
||||
|
|
@ -109,20 +108,6 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
|
|||
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const onFocus = useCallback(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
let offsetY = 150;
|
||||
if (isTablet) {
|
||||
const {width, height} = dimensions;
|
||||
const isLandscape = width > height;
|
||||
offsetY = (isLandscape ? 270 : 150);
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
keyboardAwareRef.current?.scrollToPosition(0, offsetY);
|
||||
});
|
||||
}
|
||||
}, [dimensions]);
|
||||
|
||||
const handleInput = useCallback((userToken: string) => {
|
||||
setToken(userToken);
|
||||
setError('');
|
||||
|
|
@ -156,6 +141,8 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
|
|||
};
|
||||
}, []);
|
||||
|
||||
useAvoidKeyboard(keyboardAwareRef, 2);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = {
|
||||
componentDidAppear: () => {
|
||||
|
|
@ -168,7 +155,7 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
|
|||
const unsubscribe = Navigation.events().registerComponentListener(listener, componentId);
|
||||
|
||||
return () => unsubscribe.remove();
|
||||
}, [dimensions]);
|
||||
}, [componentId, dimensions, translateX]);
|
||||
|
||||
useEffect(() => {
|
||||
translateX.value = 0;
|
||||
|
|
@ -190,7 +177,7 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
|
|||
<KeyboardAwareScrollView
|
||||
bounces={false}
|
||||
contentContainerStyle={styles.innerContainer}
|
||||
enableAutomaticScroll={Platform.OS === 'android'}
|
||||
enableAutomaticScroll={false}
|
||||
enableOnAndroid={false}
|
||||
enableResetScrollToCoords={true}
|
||||
extraScrollHeight={0}
|
||||
|
|
@ -226,7 +213,6 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
|
|||
keyboardType='numeric'
|
||||
label={formatMessage({id: 'login_mfa.token', defaultMessage: 'Enter MFA Token'})}
|
||||
onChangeText={handleInput}
|
||||
onFocus={onFocus}
|
||||
onSubmitEditing={submit}
|
||||
returnKeyType='go'
|
||||
spellCheck={false}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Button} from '@rneui/base';
|
||||
import React, {type MutableRefObject, useCallback, useEffect, useRef} from 'react';
|
||||
import React, {type RefObject, useCallback, useRef} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, Platform, useWindowDimensions, View} from 'react-native';
|
||||
import {Keyboard, View} from 'react-native';
|
||||
|
||||
import FloatingTextInput, {type FloatingTextInputRef} from '@components/floating_text_input_label';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import Loading from '@components/loading';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {useAvoidKeyboard} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
|
@ -27,8 +27,7 @@ type Props = {
|
|||
handleConnect: () => void;
|
||||
handleDisplayNameTextChanged: (text: string) => void;
|
||||
handleUrlTextChanged: (text: string) => void;
|
||||
isModal?: boolean;
|
||||
keyboardAwareRef: MutableRefObject<KeyboardAwareScrollView | null>;
|
||||
keyboardAwareRef: RefObject<KeyboardAwareScrollView>;
|
||||
theme: Theme;
|
||||
url?: string;
|
||||
urlError?: string;
|
||||
|
|
@ -79,65 +78,27 @@ const ServerForm = ({
|
|||
handleConnect,
|
||||
handleDisplayNameTextChanged,
|
||||
handleUrlTextChanged,
|
||||
isModal,
|
||||
keyboardAwareRef,
|
||||
theme,
|
||||
url = '',
|
||||
urlError,
|
||||
}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
const dimensions = useWindowDimensions();
|
||||
const displayNameRef = useRef<FloatingTextInputRef>(null);
|
||||
const urlRef = useRef<FloatingTextInputRef>(null);
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const focus = () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
let offsetY = isModal ? 120 : 160;
|
||||
if (isTablet) {
|
||||
const {width, height} = dimensions;
|
||||
const isLandscape = width > height;
|
||||
offsetY = isLandscape ? 230 : 100;
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
keyboardAwareRef.current?.scrollToPosition(0, offsetY);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onBlur = useCallback(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
const reset = !displayNameRef.current?.isFocused() && !urlRef.current?.isFocused();
|
||||
if (reset) {
|
||||
keyboardAwareRef.current?.scrollToPosition(0, 0);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
useAvoidKeyboard(keyboardAwareRef);
|
||||
|
||||
const onConnect = useCallback(() => {
|
||||
Keyboard.dismiss();
|
||||
handleConnect();
|
||||
}, [buttonDisabled, connecting, displayName, theme, url]);
|
||||
|
||||
const onFocus = useCallback(() => {
|
||||
focus();
|
||||
}, [dimensions]);
|
||||
}, [handleConnect]);
|
||||
|
||||
const onUrlSubmit = useCallback(() => {
|
||||
displayNameRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'ios' && isTablet) {
|
||||
if (urlRef.current?.isFocused() || displayNameRef.current?.isFocused()) {
|
||||
focus();
|
||||
} else {
|
||||
keyboardAwareRef.current?.scrollToPosition(0, 0);
|
||||
}
|
||||
}
|
||||
}, [dimensions, isTablet]);
|
||||
|
||||
const buttonType = buttonDisabled ? 'disabled' : 'default';
|
||||
const styleButtonText = buttonTextStyle(theme, 'lg', 'primary', buttonType);
|
||||
const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary', buttonType);
|
||||
|
|
@ -176,9 +137,7 @@ const ServerForm = ({
|
|||
id: 'mobile.components.select_server_view.enterServerUrl',
|
||||
defaultMessage: 'Enter Server URL',
|
||||
})}
|
||||
onBlur={onBlur}
|
||||
onChangeText={handleUrlTextChanged}
|
||||
onFocus={onFocus}
|
||||
onSubmitEditing={onUrlSubmit}
|
||||
ref={urlRef}
|
||||
returnKeyType='next'
|
||||
|
|
@ -198,9 +157,7 @@ const ServerForm = ({
|
|||
id: 'mobile.components.select_server_view.displayName',
|
||||
defaultMessage: 'Display Name',
|
||||
})}
|
||||
onBlur={onBlur}
|
||||
onChangeText={handleDisplayNameTextChanged}
|
||||
onFocus={onFocus}
|
||||
onSubmitEditing={onConnect}
|
||||
ref={displayNameRef}
|
||||
returnKeyType='done'
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ const Server = ({
|
|||
<KeyboardAwareScrollView
|
||||
bounces={false}
|
||||
contentContainerStyle={styles.scrollContainer}
|
||||
enableAutomaticScroll={Platform.OS === 'android'}
|
||||
enableAutomaticScroll={false}
|
||||
enableOnAndroid={false}
|
||||
enableResetScrollToCoords={true}
|
||||
extraScrollHeight={20}
|
||||
|
|
@ -400,7 +400,6 @@ const Server = ({
|
|||
handleConnect={handleConnect}
|
||||
handleDisplayNameTextChanged={handleDisplayNameTextChanged}
|
||||
handleUrlTextChanged={handleUrlTextChanged}
|
||||
isModal={isModal}
|
||||
keyboardAwareRef={keyboardAwareRef}
|
||||
theme={theme}
|
||||
url={url}
|
||||
|
|
|
|||
Loading…
Reference in a new issue