Skip to content

Commit

Permalink
feat: enter pin for biometrics ui update (#1422)
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Lippa <[email protected]>
  • Loading branch information
LippaC-OPS authored Feb 18, 2025
1 parent 053aeac commit ebfc6a7
Show file tree
Hide file tree
Showing 10 changed files with 633 additions and 74 deletions.
4 changes: 3 additions & 1 deletion packages/legacy/core/App/components/views/KeyboardView.tsx
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
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

0 comments on commit ebfc6a7

Please sign in to comment.