diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/forms/TextField.tsx | 8 | ||||
-rw-r--r-- | src/components/icons/Lock.tsx | 5 | ||||
-rw-r--r-- | src/components/icons/Pencil.tsx | 5 | ||||
-rw-r--r-- | src/screens/Login/ChooseAccountForm.tsx | 95 | ||||
-rw-r--r-- | src/screens/Login/LoginForm.tsx (renamed from src/view/com/auth/login/LoginForm.tsx) | 204 | ||||
-rw-r--r-- | src/screens/Login/index.tsx | 4 | ||||
-rw-r--r-- | src/view/com/auth/server-input/index.tsx | 2 |
7 files changed, 169 insertions, 154 deletions
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx index b37f4bfae..3cffe5b2b 100644 --- a/src/components/forms/TextField.tsx +++ b/src/components/forms/TextField.tsx @@ -14,6 +14,7 @@ import {useTheme, atoms as a, web, android} from '#/alf' import {Text} from '#/components/Typography' import {useInteractionState} from '#/components/hooks/useInteractionState' import {Props as SVGIconProps} from '#/components/icons/common' +import {mergeRefs} from '#/lib/merge-refs' const Context = React.createContext<{ inputRef: React.RefObject<TextInput> | null @@ -128,6 +129,7 @@ export type InputProps = Omit<TextInputProps, 'value' | 'onChangeText'> & { value: string onChangeText: (value: string) => void isInvalid?: boolean + inputRef?: React.RefObject<TextInput> } export function createInput(Component: typeof TextInput) { @@ -137,6 +139,7 @@ export function createInput(Component: typeof TextInput) { value, onChangeText, isInvalid, + inputRef, ...rest }: InputProps) { const t = useTheme() @@ -161,19 +164,22 @@ export function createInput(Component: typeof TextInput) { ) } + const refs = mergeRefs([ctx.inputRef, inputRef!].filter(Boolean)) + return ( <> <Component accessibilityHint={undefined} {...rest} accessibilityLabel={label} - ref={ctx.inputRef} + ref={refs} value={value} onChangeText={onChangeText} onFocus={ctx.onFocus} onBlur={ctx.onBlur} placeholder={placeholder || label} placeholderTextColor={t.palette.contrast_500} + keyboardAppearance={t.name === 'light' ? 'light' : 'dark'} hitSlop={HITSLOP_20} style={[ a.relative, diff --git a/src/components/icons/Lock.tsx b/src/components/icons/Lock.tsx new file mode 100644 index 000000000..87830b379 --- /dev/null +++ b/src/components/icons/Lock.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Lock_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M7 7a5 5 0 0 1 10 0v2h1a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-9a2 2 0 0 1 2-2h1V7Zm-1 4v9h12v-9H6Zm9-2H9V7a3 3 0 1 1 6 0v2Zm-3 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0v-3a1 1 0 0 1 1-1Z', +}) diff --git a/src/components/icons/Pencil.tsx b/src/components/icons/Pencil.tsx new file mode 100644 index 000000000..1b7fc17cf --- /dev/null +++ b/src/components/icons/Pencil.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Pencil_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M13.586 1.5a2 2 0 0 1 2.828 0L19.5 4.586a2 2 0 0 1 0 2.828l-13 13A2 2 0 0 1 5.086 21H1a1 1 0 0 1-1-1v-4.086A2 2 0 0 1 .586 14.5l13-13ZM15 2.914l-13 13V19h3.086l13-13L15 2.914ZM11 20a1 1 0 0 1 1-1h7a1 1 0 1 1 0 2h-7a1 1 0 0 1-1-1Z', +}) diff --git a/src/screens/Login/ChooseAccountForm.tsx b/src/screens/Login/ChooseAccountForm.tsx index 99d1beb89..f5b3c2a86 100644 --- a/src/screens/Login/ChooseAccountForm.tsx +++ b/src/screens/Login/ChooseAccountForm.tsx @@ -13,7 +13,7 @@ import {useProfileQuery} from '#/state/queries/profile' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import * as Toast from '#/view/com/util/Toast' import {Button} from '#/components/Button' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Text} from '#/components/Typography' import {ChevronRight_Stroke2_Corner0_Rounded as Chevron} from '#/components/icons/Chevron' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' @@ -106,6 +106,7 @@ export const ChooseAccountForm = ({ const {accounts, currentAccount} = useSession() const {initSession} = useSessionApi() const {setShowLoggedOut} = useLoggedOutViewControls() + const {gtMobile} = useBreakpoints() React.useEffect(() => { screen('Choose Account') @@ -133,50 +134,54 @@ export const ChooseAccountForm = ({ return ( <ScrollView testID="chooseAccountForm" style={styles.maxHeight}> - <Text style={[a.mt_md, a.mb_lg, a.font_bold]}> - <Trans>Sign in as...</Trans> - </Text> - <Group> - {accounts.map(account => ( - <AccountItem - key={account.did} - account={account} - onSelect={onSelect} - isCurrentAccount={account.did === currentAccount?.did} - /> - ))} - <TouchableOpacity - testID="chooseNewAccountBtn" - style={[a.flex_1]} - onPress={() => onSelectAccount(undefined)} - accessibilityRole="button" - accessibilityLabel={_(msg`Login to account that is not listed`)} - accessibilityHint=""> - <View style={[a.flex_row, a.flex_row, a.align_center, {height: 48}]}> - <Text - style={[ - a.align_baseline, - a.flex_1, - a.flex_row, - a.py_sm, - {paddingLeft: 48}, - ]}> - <Trans>Other account</Trans> - </Text> - <Chevron size="sm" style={[t.atoms.text, a.mr_md]} /> - </View> - </TouchableOpacity> - </Group> - <View style={[a.flex_row, a.mt_lg]}> - <Button - label={_(msg`Back`)} - variant="solid" - color="secondary" - size="small" - onPress={onPressBack}> - <Trans>Back</Trans> - </Button> - <View style={[a.flex_1]} /> + <View style={!gtMobile && a.px_lg}> + <Text + style={[a.mt_md, a.mb_lg, a.font_bold, t.atoms.text_contrast_medium]}> + <Trans>Sign in as...</Trans> + </Text> + <Group> + {accounts.map(account => ( + <AccountItem + key={account.did} + account={account} + onSelect={onSelect} + isCurrentAccount={account.did === currentAccount?.did} + /> + ))} + <TouchableOpacity + testID="chooseNewAccountBtn" + style={[a.flex_1]} + onPress={() => onSelectAccount(undefined)} + accessibilityRole="button" + accessibilityLabel={_(msg`Login to account that is not listed`)} + accessibilityHint=""> + <View + style={[a.flex_row, a.flex_row, a.align_center, {height: 48}]}> + <Text + style={[ + a.align_baseline, + a.flex_1, + a.flex_row, + a.py_sm, + {paddingLeft: 48}, + ]}> + <Trans>Other account</Trans> + </Text> + <Chevron size="sm" style={[t.atoms.text, a.mr_md]} /> + </View> + </TouchableOpacity> + </Group> + <View style={[a.flex_row, a.mt_lg]}> + <Button + label={_(msg`Back`)} + variant="solid" + color="secondary" + size="small" + onPress={onPressBack}> + {_(msg`Back`)} + </Button> + <View style={[a.flex_1]} /> + </View> </View> </ScrollView> ) diff --git a/src/view/com/auth/login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx index fdba9f203..8ac4fa359 100644 --- a/src/view/com/auth/login/LoginForm.tsx +++ b/src/screens/Login/LoginForm.tsx @@ -6,28 +6,31 @@ import { TouchableOpacity, View, } from 'react-native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' import {ComAtprotoServerDescribeServer} from '@atproto/api' +import {Trans, msg} from '@lingui/macro' + import {useAnalytics} from 'lib/analytics/analytics' -import {Text} from '../../util/text/Text' import {s} from 'lib/styles' import {createFullHandle} from 'lib/strings/handles' import {toNiceDomain} from 'lib/strings/url-helpers' import {isNetworkError} from 'lib/strings/errors' -import {usePalette} from 'lib/hooks/usePalette' -import {useTheme} from 'lib/ThemeContext' import {useSessionApi} from '#/state/session' import {cleanError} from 'lib/strings/errors' import {logger} from '#/logger' -import {Trans, msg} from '@lingui/macro' -import {styles} from './styles' +import {styles} from '../../view/com/auth/login/styles' import {useLingui} from '@lingui/react' import {useDialogControl} from '#/components/Dialog' - -import {ServerInputDialog} from '../server-input' +import {ServerInputDialog} from '../../view/com/auth/server-input' +import {Button} from '#/components/Button' +import {isAndroid} from '#/platform/detection' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Text} from '#/components/Typography' +import * as TextField from '#/components/forms/TextField' +import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At' +import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' +import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' +import {Pencil_Stroke2_Corner0_Rounded as Pencil} from '#/components/icons/Pencil' +import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema @@ -40,7 +43,6 @@ export const LoginForm = ({ setServiceUrl, onPressRetryConnect, onPressBack, - onPressForgotPassword, }: { error: string serviceUrl: string @@ -53,8 +55,7 @@ export const LoginForm = ({ onPressForgotPassword: () => void }) => { const {track} = useAnalytics() - const pal = usePalette('default') - const theme = useTheme() + const t = useTheme() const [isProcessing, setIsProcessing] = useState<boolean>(false) const [identifier, setIdentifier] = useState<string>(initialHandle) const [password, setPassword] = useState<string>('') @@ -62,6 +63,7 @@ export const LoginForm = ({ const {_} = useLingui() const {login} = useSessionApi() const serverInputControl = useDialogControl() + const {gtMobile} = useBreakpoints() const onPressSelectService = () => { serverInputControl.open() @@ -127,55 +129,54 @@ export const LoginForm = ({ const isReady = !!serviceDescription && !!identifier && !!password return ( - <View testID="loginForm"> + <View testID="loginForm" style={[a.gap_lg, !gtMobile && a.px_lg]}> <ServerInputDialog control={serverInputControl} onSelect={setServiceUrl} /> - <Text type="sm-bold" style={[pal.text, styles.groupLabel]}> - <Trans>Sign into</Trans> - </Text> - <View style={[pal.borderDark, styles.group]}> - <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> - <FontAwesomeIcon - icon="globe" - style={[pal.textLight, styles.groupContentIcon]} - /> - <TouchableOpacity - testID="loginSelectServiceButton" - style={styles.textBtn} - onPress={onPressSelectService} - accessibilityRole="button" - accessibilityLabel={_(msg`Select service`)} - accessibilityHint={_(msg`Sets server for the Bluesky client`)}> - <Text type="xl" style={[pal.text, styles.textBtnLabel]}> - {toNiceDomain(serviceUrl)} - </Text> - <View style={[pal.btn, styles.textBtnFakeInnerBtn]}> - <FontAwesomeIcon - icon="pen" - size={12} - style={pal.textLight as FontAwesomeIconStyle} - /> - </View> - </TouchableOpacity> - </View> + <View> + <TextField.Label> + <Trans>Hosting provider</Trans> + </TextField.Label> + <TouchableOpacity + accessibilityRole="button" + style={[ + a.w_full, + a.flex_row, + a.align_center, + a.rounded_sm, + a.px_md, + a.gap_xs, + {paddingVertical: isAndroid ? 14 : 9}, + t.atoms.bg_contrast_25, + ]} + onPress={onPressSelectService}> + <TextField.Icon icon={Globe} /> + <Text style={[a.text_md]}>{toNiceDomain(serviceUrl)}</Text> + <View + style={[ + a.rounded_sm, + t.atoms.bg_contrast_100, + {marginLeft: 'auto', left: 6, padding: 6}, + ]}> + <Pencil + style={{color: t.palette.contrast_500}} + height={18} + width={18} + /> + </View> + </TouchableOpacity> </View> - <Text type="sm-bold" style={[pal.text, styles.groupLabel]}> - <Trans>Account</Trans> - </Text> - <View style={[pal.borderDark, styles.group]}> - <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> - <FontAwesomeIcon - icon="at" - style={[pal.textLight, styles.groupContentIcon]} - /> - <TextInput + <View> + <TextField.Label> + <Trans>Account</Trans> + </TextField.Label> + <TextField.Root> + <TextField.Icon icon={At} /> + <TextField.Input testID="loginUsernameInput" - style={[pal.text, styles.textInput]} - placeholder={_(msg`Username or email address`)} - placeholderTextColor={pal.colors.textLight} + label={_(msg`Username or email address`)} autoCapitalize="none" autoFocus autoCorrect={false} @@ -186,35 +187,29 @@ export const LoginForm = ({ passwordInputRef.current?.focus() }} blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field - keyboardAppearance={theme.colorScheme} value={identifier} onChangeText={str => setIdentifier((str || '').toLowerCase().trim()) } editable={!isProcessing} - accessibilityLabel={_(msg`Username or email address`)} accessibilityHint={_( msg`Input the username or email address you used at signup`, )} /> - </View> - <View style={[pal.borderDark, styles.groupContent]}> - <FontAwesomeIcon - icon="lock" - style={[pal.textLight, styles.groupContentIcon]} - /> - <TextInput + </TextField.Root> + </View> + <View> + <TextField.Root> + <TextField.Icon icon={Lock} /> + <TextField.Input testID="loginPasswordInput" - ref={passwordInputRef} - style={[pal.text, styles.textInput]} - placeholder="Password" - placeholderTextColor={pal.colors.textLight} + inputRef={passwordInputRef} + label={_(msg`Password`)} autoCapitalize="none" autoCorrect={false} autoComplete="password" returnKeyType="done" enablesReturnKeyAutomatically={true} - keyboardAppearance={theme.colorScheme} secureTextEntry={true} textContentType="password" clearButtonMode="while-editing" @@ -223,14 +218,13 @@ export const LoginForm = ({ onSubmitEditing={onPressNext} blurOnSubmit={false} // HACK: https://github.com/facebook/react-native/issues/21911#issuecomment-558343069 Keyboard blur behavior is now handled in onSubmitEditing editable={!isProcessing} - accessibilityLabel={_(msg`Password`)} accessibilityHint={ identifier === '' ? _(msg`Input your password`) : _(msg`Input the password tied to ${identifier}`) } /> - <TouchableOpacity + {/* <TouchableOpacity testID="forgotPasswordButton" style={styles.textInputInnerBtn} onPress={onPressForgotPassword} @@ -240,57 +234,57 @@ export const LoginForm = ({ <Text style={pal.link}> <Trans>Forgot</Trans> </Text> - </TouchableOpacity> - </View> + </TouchableOpacity> */} + </TextField.Root> </View> {error ? ( - <View style={styles.error}> - <View style={styles.errorIcon}> - <FontAwesomeIcon icon="exclamation" style={s.white} size={10} /> - </View> - <View style={s.flex1}> + <View style={[styles.error, {marginHorizontal: 0}]}> + <Warning style={s.white} size="sm" /> + <View style={(a.flex_1, a.ml_sm)}> <Text style={[s.white, s.bold]}>{error}</Text> </View> </View> ) : undefined} - <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> - <TouchableOpacity onPress={onPressBack} accessibilityRole="button"> - <Text type="xl" style={[pal.link, s.pl5]}> - <Trans>Back</Trans> - </Text> - </TouchableOpacity> + <View style={[a.flex_row, a.align_center]}> + <Button + label={_(msg`Back`)} + variant="solid" + color="secondary" + size="small" + onPress={onPressBack}> + {_(msg`Back`)} + </Button> <View style={s.flex1} /> {!serviceDescription && error ? ( - <TouchableOpacity + <Button testID="loginRetryButton" - onPress={onPressRetryConnect} - accessibilityRole="button" - accessibilityLabel={_(msg`Retry`)} - accessibilityHint={_(msg`Retries login`)}> - <Text type="xl-bold" style={[pal.link, s.pr5]}> - <Trans>Retry</Trans> - </Text> - </TouchableOpacity> + label={_(msg`Retry`)} + accessibilityHint={_(msg`Retries login`)} + variant="solid" + color="secondary" + size="small" + onPress={onPressRetryConnect}> + {_(msg`Retry`)} + </Button> ) : !serviceDescription ? ( <> <ActivityIndicator /> - <Text type="xl" style={[pal.textLight, s.pl10]}> + <Text style={[t.atoms.text_contrast_high, a.pl_md]}> <Trans>Connecting...</Trans> </Text> </> ) : isProcessing ? ( <ActivityIndicator /> ) : isReady ? ( - <TouchableOpacity - testID="loginNextButton" - onPress={onPressNext} - accessibilityRole="button" - accessibilityLabel={_(msg`Go to next`)} - accessibilityHint={_(msg`Navigates to the next screen`)}> - <Text type="xl-bold" style={[pal.link, s.pr5]}> - <Trans>Next</Trans> - </Text> - </TouchableOpacity> + <Button + label={_(msg`Next`)} + accessibilityHint={_(msg`Navigates to the next screen`)} + variant="solid" + color="primary" + size="small" + onPress={onPressNext}> + {_(msg`Next`)} + </Button> ) : undefined} </View> </View> diff --git a/src/screens/Login/index.tsx b/src/screens/Login/index.tsx index f2cfde550..3bd2df60b 100644 --- a/src/screens/Login/index.tsx +++ b/src/screens/Login/index.tsx @@ -1,4 +1,5 @@ import React from 'react' +import {KeyboardAvoidingView} from 'react-native' import {useAnalytics} from '#/lib/analytics/analytics' import {useLingui} from '@lingui/react' import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' @@ -9,12 +10,11 @@ import {useServiceQuery} from '#/state/queries/service' import {msg} from '@lingui/macro' import {logger} from '#/logger' import {atoms as a} from '#/alf' -import {KeyboardAvoidingView} from 'react-native' import {ChooseAccountForm} from './ChooseAccountForm' import {ForgotPasswordForm} from '#/view/com/auth/login/ForgotPasswordForm' import {SetNewPasswordForm} from '#/view/com/auth/login/SetNewPasswordForm' import {PasswordUpdatedForm} from '#/view/com/auth/login/PasswordUpdatedForm' -import {LoginForm} from '#/view/com/auth/login/LoginForm' +import {LoginForm} from '#/screens/Login/LoginForm' enum Forms { Login, diff --git a/src/view/com/auth/server-input/index.tsx b/src/view/com/auth/server-input/index.tsx index 32b5a3141..81f4bdf93 100644 --- a/src/view/com/auth/server-input/index.tsx +++ b/src/view/com/auth/server-input/index.tsx @@ -67,7 +67,7 @@ export function ServerInputDialog({ return ( <Dialog.Outer control={control} - nativeOptions={{sheet: {snapPoints: ['100%']}}} + nativeOptions={{sheet: {snapPoints: ['80', '100%']}}} onClose={onClose}> <Dialog.Handle /> |