about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/forms/TextField.tsx8
-rw-r--r--src/components/icons/Lock.tsx5
-rw-r--r--src/components/icons/Pencil.tsx5
-rw-r--r--src/screens/Login/ChooseAccountForm.tsx95
-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.tsx4
-rw-r--r--src/view/com/auth/server-input/index.tsx2
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 />