Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enter pin for biometrics ui update #1422

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react'
import { KeyboardAvoidingView, Platform, ScrollView } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useTheme } from '../../contexts/theme'

const KeyboardView: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { ColorPallet } = useTheme()
return (
<SafeAreaView style={{ flex: 1 }} edges={['bottom', 'left', 'right']}>
<SafeAreaView style={{ flex: 1, backgroundColor: ColorPallet.brand.primaryBackground }} edges={['bottom', 'left', 'right']}>
<KeyboardAvoidingView
style={{ flex: 1 }}
// below property is the distance to account for between the top of the screen and the top of the view. It is at most 100 with max zoom + font settings
Expand Down
4 changes: 4 additions & 0 deletions packages/legacy/core/App/localization/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ const translation = {
"AppSettingChangedEnterPIN": "Please confirm your change by entering your wallet PIN.",
"AppSettingSave": "Save",
"AppSettingCancel": "Cancel",
"ChangeBiometricsHeader": "Change in biometrics",
"ChangeBiometricsSubtext": "Please enter your PIN to confirm a change in biometrics",
"ChangeBiometricsInputLabel": "Enter your wallet PIN",
"ChangeBiometricsInputLabelParenthesis": "(Required)",
},
"AttemptLockout": {
"Title": "Your wallet is temporarily locked",
Expand Down
4 changes: 4 additions & 0 deletions packages/legacy/core/App/localization/fr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ const translation = {
"AppSettingChangedEnterPIN": "Veuillez confirmer la modification en saisissant votre NIP.",
"AppSettingSave": "Enregistrer",
"AppSettingCancel": "Annuler",
"ChangeBiometricsHeader": "Change in biometrics (FR)",
"ChangeBiometricsSubtext": "Please enter your PIN to confirm a change in biometrics (FR)",
"ChangeBiometricsInputLabel": "Enter your wallet PIN (FR)",
"ChangeBiometricsInputLabelParenthesis": "(Required) (FR)",

},
"AttemptLockout": {
Expand Down
4 changes: 4 additions & 0 deletions packages/legacy/core/App/localization/pt-br/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ const translation = {
"AppSettingChangedEnterPIN": "Por favor, confirme sua alteração digitando o seu PIN.",
"AppSettingSave": "Salvar",
"AppSettingCancel": "Cancelar",
"ChangeBiometricsHeader": "Change in biometrics (PT-BR)",
"ChangeBiometricsSubtext": "Please enter your PIN to confirm a change in biometrics (PT-BR)",
"ChangeBiometricsInputLabel": "Enter your wallet PIN (PT-BR)",
"ChangeBiometricsInputLabelParenthesis": "(Required) (PT-BR)",
},
"Biometry": {
"Toggle": "Habilitar Biometria",
Expand Down
79 changes: 59 additions & 20 deletions packages/legacy/core/App/screens/PINEnter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface PINEnterProps {
export enum PINEntryUsage {
PINCheck,
WalletUnlock,
ChangeBiometrics,
}

const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated, usage = PINEntryUsage.WalletUnlock, onCancelAuth }) => {
Expand All @@ -54,19 +55,19 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated, usage = PINEntryU
const [inlineMessageField, setInlineMessageField] = useState<InlineMessageProps>()
const [inlineMessages] = useServices([TOKENS.INLINE_ERRORS])
const [alertModalMessage, setAlertModalMessage] = useState('')
// Temporary until all use cases are built with the new design
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is meant by "all use cases" here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still have this question. Do you mean all extensions of Bifold or all uses of the PINEnter screen? Is PINEntryUsage.PINCheck eventually going to be the same as PINEntryUsage.ChangeBiometrics?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that this screen for all the different use cases will have similar design changes I just didn't put them through all at once in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh ok so by use cases you mean the different PINEntryUsage types and the UX folks don't want to change the styles for all of them at once yet. I understand now, all good 👍

const isNewDesign = usage === PINEntryUsage.ChangeBiometrics

const style = StyleSheet.create({
screenContainer: {
height: '100%',
backgroundColor: ColorPallet.brand.primaryBackground,
padding: 20,
justifyContent: 'space-between',
justifyContent: isNewDesign ? 'flex-start' : 'space-between',
},

// below used as helpful labels for views, no properties needed atp
contentContainer: {},
controlsContainer: {},

buttonContainer: {
width: '100%',
},
Expand All @@ -78,7 +79,10 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated, usage = PINEntryU
...TextTheme.normal,
alignSelf: 'auto',
textAlign: 'left',
marginBottom: 16,
marginBottom: isNewDesign ? 40 : 16,
},
parenthesisText: {
...TextTheme.caption,
},
modalText: {
...TextTheme.popupModalText,
Expand All @@ -92,18 +96,43 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated, usage = PINEntryU
},
title: {
...TextTheme.headingTwo,
marginBottom: 20,
marginTop: isNewDesign ? 20 : 0,
marginBottom: isNewDesign ? 40 : 20,
},
subTitle: {
...TextTheme.labelSubtitle,
marginBottom: 20,
},
subText: {
...TextTheme.bold,
marginBottom: 4,
marginBottom: isNewDesign ? 20 : 4,
},
})

const inputLabelText = {
[PINEntryUsage.ChangeBiometrics]: t('PINEnter.ChangeBiometricsInputLabel'),
[PINEntryUsage.PINCheck]: t('PINEnter.AppSettingChangedEnterPIN'),
[PINEntryUsage.WalletUnlock]: t('PINEnter.EnterPIN'),
}

const inputTestId = {
[PINEntryUsage.ChangeBiometrics]: 'BiometricChangedEnterPIN',
[PINEntryUsage.PINCheck]: 'AppSettingChangedEnterPIN',
[PINEntryUsage.WalletUnlock]: 'EnterPIN',
}

const primaryButtonText = {
[PINEntryUsage.ChangeBiometrics]: t('Global.Continue'),
[PINEntryUsage.PINCheck]: t('PINEnter.AppSettingSave'),
[PINEntryUsage.WalletUnlock]: t('PINEnter.Unlock'),
}

const primaryButtonTestId = {
[PINEntryUsage.ChangeBiometrics]: 'Continue',
[PINEntryUsage.PINCheck]: 'AppSettingSave',
[PINEntryUsage.WalletUnlock]: 'Enter',
}

const incrementDeveloperMenuCounter = useCallback(() => {
if (developerOptionCount.current >= touchCountToEnableBiometrics) {
developerOptionCount.current = 0
Expand Down Expand Up @@ -194,7 +223,7 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated, usage = PINEntryU
)

const loadWalletCredentials = useCallback(async () => {
if (usage === PINEntryUsage.PINCheck) {
if (usage === PINEntryUsage.PINCheck || PINEntryUsage.ChangeBiometrics) {
return
}

Expand Down Expand Up @@ -360,6 +389,10 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated, usage = PINEntryU
setAlertModalVisible(false)
setAuthenticated(false)
break
case PINEntryUsage.ChangeBiometrics:
setAlertModalVisible(false)
setAuthenticated(false)
break

default:
setAlertModalVisible(false)
Expand Down Expand Up @@ -414,7 +447,7 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated, usage = PINEntryU

setContinueEnabled(false)

if (usage === PINEntryUsage.PINCheck) {
if (usage === PINEntryUsage.PINCheck || PINEntryUsage.ChangeBiometrics) {
await verifyPIN(PIN)
}

Expand Down Expand Up @@ -459,6 +492,15 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated, usage = PINEntryU
return <Text style={style.helpText}>{t('PINEnter.AppSettingChanged')}</Text>
}

if (usage === PINEntryUsage.ChangeBiometrics) {
return (
<>
<Text style={style.title}>{t('PINEnter.ChangeBiometricsHeader')}</Text>
<Text style={style.helpText}>{t('PINEnter.ChangeBiometricsSubtext')}</Text>
</>
)
}

return (
<>
<Text style={style.title}>{t('PINEnter.Title')}</Text>
Expand Down Expand Up @@ -488,34 +530,31 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated, usage = PINEntryU
) : (
displayHelpText()
)}
<Text style={style.subText}>{`${
usage === PINEntryUsage.PINCheck ? t('PINEnter.AppSettingChangedEnterPIN') : t('PINEnter.EnterPIN')
}`}</Text>
<Text style={style.subText}>
{inputLabelText[usage]}
{usage === PINEntryUsage.ChangeBiometrics && <Text style={style.parenthesisText}>{` `}{t('PINEnter.ChangeBiometricsInputLabelParenthesis')}</Text>}
</Text>
<PINInput
onPINChanged={(p: string) => {
setPIN(p)
if (p.length === minPINLength) {
Keyboard.dismiss()
}
}}
testID={testIdWithKey(usage === PINEntryUsage.PINCheck ? 'AppSettingChangedEnterPIN' : 'EnterPIN')}
accessibilityLabel={
usage === PINEntryUsage.PINCheck ? t('PINEnter.AppSettingChangedEnterPIN') : t('PINEnter.EnterPIN')
}
testID={testIdWithKey(inputTestId[usage])}
accessibilityLabel={inputLabelText[usage]}
autoFocus={true}
inlineMessage={inlineMessageField}
/>
</View>
<View style={style.controlsContainer}>
<View style={style.buttonContainer}>
<Button
title={usage === PINEntryUsage.PINCheck ? t('PINEnter.AppSettingSave') : t('PINEnter.Unlock')}
title={primaryButtonText[usage]}
buttonType={ButtonType.Primary}
testID={testIdWithKey(usage === PINEntryUsage.PINCheck ? 'AppSettingSave' : 'Enter')}
testID={testIdWithKey(primaryButtonTestId[usage])}
disabled={isContinueDisabled()}
accessibilityLabel={
usage === PINEntryUsage.PINCheck ? t('PINEnter.AppSettingSave') : t('PINEnter.Unlock')
}
accessibilityLabel={primaryButtonText[usage]}
onPress={() => {
Keyboard.dismiss()
onPINInputCompleted(PIN)
Expand Down
17 changes: 13 additions & 4 deletions packages/legacy/core/App/screens/UseBiometry.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { CommonActions, useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { Header, useHeaderHeight, HeaderBackButton } from '@react-navigation/elements';
import React, { useState, useEffect, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View, Modal, ScrollView, Linking, Platform } from 'react-native'
import { PERMISSIONS, RESULTS, request, check, PermissionStatus } from 'react-native-permissions'
import { SafeAreaView } from 'react-native-safe-area-context'
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'

import Button, { ButtonType } from '../components/buttons/Button'
import ToggleButton from '../components/buttons/ToggleButton'
Expand Down Expand Up @@ -46,6 +47,8 @@ const UseBiometry: React.FC = () => {
const screenUsage = useMemo(() => {
return store.onboarding.didCompleteOnboarding ? UseBiometryUsage.ToggleOnOff : UseBiometryUsage.InitialSetup
}, [store.onboarding.didCompleteOnboarding])
const headerHeight = useHeaderHeight()
const insets = useSafeAreaInsets()

const BIOMETRY_PERMISSION = PERMISSIONS.IOS.FACE_ID

Expand Down Expand Up @@ -314,7 +317,7 @@ const UseBiometry: React.FC = () => {
<View style={{ marginTop: 'auto', margin: 20 }}>
{store.onboarding.didCompleteOnboarding || (
<Button
title={'Continue'}
title={t('Global.Continue')}
accessibilityLabel={'Continue'}
testID={testIdWithKey('Continue')}
onPress={continueTouched}
Expand All @@ -330,10 +333,16 @@ const UseBiometry: React.FC = () => {
visible={canSeeCheckPIN}
transparent={false}
animationType={'slide'}
presentationStyle="pageSheet"
presentationStyle='fullScreen'
>
<Header
title={t('Screens.EnterPIN')}
headerTitleStyle={{ marginTop: insets.top, ...TextTheme.headerTitle }}
headerStyle={{ height: headerHeight }}
headerLeft={() => <HeaderBackButton onPress={() => setCanSeeCheckPIN(false)} tintColor='white' style={{ marginTop: insets.top }} labelVisible={false} />}
/>
<PINEnter
usage={PINEntryUsage.PINCheck}
usage={PINEntryUsage.ChangeBiometrics}
setAuthenticated={onAuthenticationComplete}
onCancelAuth={setCanSeeCheckPIN}
/>
Expand Down
20 changes: 19 additions & 1 deletion packages/legacy/core/__tests__/screens/PINEnter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ContainerProvider } from '../../App/container-api'
import { MainContainer } from '../../App/container-impl'
import { AuthContext } from '../../App/contexts/auth'
import { StoreProvider, defaultState } from '../../App/contexts/store'
import PINEnter from '../../App/screens/PINEnter'
import PINEnter, { PINEntryUsage } from '../../App/screens/PINEnter'
import { testIdWithKey } from '../../App/utils/testable'
import authContext from '../contexts/auth'

Expand All @@ -29,6 +29,24 @@ describe('PINEnter Screen', () => {
expect(tree).toMatchSnapshot()
})

test('PIN Enter renders correctly for biometrics change', () => {
const main = new MainContainer(container.createChildContainer()).init()
const tree = render(
<ContainerProvider value={main}>
<StoreProvider
initialState={{
...defaultState,
}}
>
<AuthContext.Provider value={authContext}>
<PINEnter setAuthenticated={jest.fn()} usage={PINEntryUsage.ChangeBiometrics} />
</AuthContext.Provider>
</StoreProvider>
</ContainerProvider>
)
expect(tree).toMatchSnapshot()
})

test('PIN Enter renders correctly when logged out message is present', async () => {
const main = new MainContainer(container.createChildContainer()).init()
const tree = render(
Expand Down
29 changes: 26 additions & 3 deletions packages/legacy/core/__tests__/screens/UseBiometry.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { render, fireEvent, waitFor } from '@testing-library/react-native'
import React from 'react'

import { AuthContext } from '../../App/contexts/auth'
import UseBiometry from '../../App/screens/UseBiometry'
import { testIdWithKey } from '../../App/utils/testable'
Expand All @@ -18,6 +17,30 @@ const mockedRequest = request as jest.MockedFunction<typeof request>

jest.spyOn(Linking, 'openSettings').mockImplementation(() => Promise.resolve())

jest.mock('@react-navigation/elements', () => ({
Header: jest.fn().mockImplementation(() => {
const Component = () => null;
Component.displayName = 'Header';
return Component;
}),
HeaderBackButton: jest.fn().mockImplementation(() => {
const Component = () => null;
Component.displayName = 'HeaderBackButton';
return Component;
}),
useHeaderHeight: jest.fn().mockReturnValue(150)
}));

jest.mock('react-native-safe-area-context', () => ({
useSafeAreaInsets: jest.fn().mockReturnValue({
top: 25,
bottom: 25,
left: 0,
right: 0
}),
SafeAreaView: jest.fn().mockImplementation(({children}) => children)
}));

const customStore = {
...testDefaultState,
preferences: {
Expand Down Expand Up @@ -46,7 +69,7 @@ describe('UseBiometry Screen', () => {
const tree = render(
<BasicAppContext>
<AuthContext.Provider value={authContext}>
<UseBiometry />
<UseBiometry />
</AuthContext.Provider>
</BasicAppContext>
)
Expand All @@ -63,7 +86,7 @@ describe('UseBiometry Screen', () => {
const tree = render(
<BasicAppContext>
<AuthContext.Provider value={authContext}>
<UseBiometry />
<UseBiometry />
</AuthContext.Provider>
</BasicAppContext>
)
Expand Down
Loading