Skip to content

Commit

Permalink
chore: display error messages for PIN screens inline mode (#1253)
Browse files Browse the repository at this point in the history
Signed-off-by: Mostafa Gamal <[email protected]>
Signed-off-by: Mostafa Gamal <[email protected]>
Signed-off-by: Mostafa Youssef <[email protected]>
Co-authored-by: Mostafa Youssef <[email protected]>
  • Loading branch information
MosCD3 and Mostafa Youssef authored Nov 7, 2024
1 parent 4766aa2 commit 31734d5
Show file tree
Hide file tree
Showing 13 changed files with 479 additions and 332 deletions.
90 changes: 0 additions & 90 deletions packages/legacy/app/App.tsx

This file was deleted.

12 changes: 12 additions & 0 deletions packages/legacy/core/App/assets/img/error-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions packages/legacy/core/App/assets/img/exclamation-mark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions packages/legacy/core/App/components/inputs/InlineErrorText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react'
import { View, StyleSheet, Text } from 'react-native'

import { useTheme } from '../../contexts/theme'
import { SvgProps } from 'react-native-svg'
import { InlineErrorConfig } from '../../types/error'

export enum InlineErrorType {
error,
warning,
}

export interface InlineMessageProps {
message: string
inlineType: InlineErrorType
config: InlineErrorConfig
}

const InlineErrorText: React.FC<InlineMessageProps> = ({ message, inlineType, config }) => {
const { InputInlineMessage } = useTheme()
const style = StyleSheet.create({
container: {
flexDirection: 'row',
alignContent: 'center',
marginVertical: 5,
paddingRight: 20,
},
icon: { marginRight: 4 },
})

const color =
inlineType === InlineErrorType.warning
? InputInlineMessage.inlineWarningText.color
: InputInlineMessage.inlineErrorText.color

const props: SvgProps = { height: 16, width: 16, color: color, style: style.icon }

return (
<View style={[style.container, config.style]}>
{inlineType === InlineErrorType.warning ? (
<InputInlineMessage.InlineWarningIcon {...props} />
) : (
<InputInlineMessage.InlineErrorIcon {...props} />
)}
<Text style={[InputInlineMessage.inlineErrorText]}>{message}</Text>
</View>
)
}

export default InlineErrorText
108 changes: 64 additions & 44 deletions packages/legacy/core/App/components/inputs/PINInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ import Icon from 'react-native-vector-icons/MaterialIcons'
import { hitSlop, minPINLength } from '../../constants'
import { useTheme } from '../../contexts/theme'
import { testIdWithKey } from '../../utils/testable'
import InlineErrorText, { InlineMessageProps } from './InlineErrorText'
import { InlineErrorPosition } from '../../types/error'

interface PINInputProps {
label?: string
onPINChanged?: (PIN: string) => void
testID?: string
accessibilityLabel?: string
autoFocus?: boolean
inlineMessage?: InlineMessageProps
}

const PINInputComponent = (
{ label, onPINChanged, testID, accessibilityLabel, autoFocus = false }: PINInputProps,
{ label, onPINChanged, testID, accessibilityLabel, autoFocus = false, inlineMessage }: PINInputProps,
ref: Ref<TextInput>
) => {
// const accessible = accessibilityLabel && accessibilityLabel !== '' ? true : false
Expand Down Expand Up @@ -58,53 +61,70 @@ const PINInputComponent = (
paddingHorizontal: 10,
},
})
const content = () => (
<View style={PINInputTheme.labelAndFieldContainer}>
<View style={style.codeFieldContainer}>
<CodeField
{...props}
testID={testID}
accessibilityLabel={accessibilityLabel}
accessible
value={PIN}
rootStyle={PINInputTheme.codeFieldRoot}
onChangeText={onChangeText}
cellCount={minPINLength}
keyboardType="numeric"
textContentType="password"
renderCell={({ index, symbol, isFocused }) => {
let child: React.ReactNode | string = ''
if (symbol) {
child = showPIN ? symbol : '●' // Show or hide PIN
} else if (isFocused) {
child = <Cursor />
}
return (
<View key={index} style={style.cell} onLayout={getCellOnLayoutHandler(index)}>
<Text style={style.cellText} maxFontSizeMultiplier={1}>
{child}
</Text>
</View>
)
}}
autoFocus={autoFocus}
ref={ref}
/>
</View>
<TouchableOpacity
style={style.hideIcon}
accessibilityLabel={showPIN ? t('PINCreate.Hide') : t('PINCreate.Show')}
accessibilityRole={'button'}
testID={showPIN ? testIdWithKey('Hide') : testIdWithKey('Show')}
onPress={() => setShowPIN(!showPIN)}
hitSlop={hitSlop}
>
<Icon color={PINInputTheme.icon.color} name={showPIN ? 'visibility-off' : 'visibility'} size={30} />
</TouchableOpacity>
</View>
)

const inlineMessageView = ({ message, inlineType, config }: InlineMessageProps) => (
<InlineErrorText message={message} inlineType={inlineType} config={config} />
)
const inlineMessagePlaceholder = (placment: InlineErrorPosition) => {
if (inlineMessage && inlineMessage.config.position === placment) {
return inlineMessageView(inlineMessage)
}
//This is a fallback in case no position provided
if (inlineMessage && placment === InlineErrorPosition.Above && !inlineMessage.config.position) {
return inlineMessageView(inlineMessage)
}
}
return (
<View style={style.container}>
{label && <Text style={[TextTheme.label, { marginBottom: 8 }]}>{label}</Text>}
<View style={PINInputTheme.labelAndFieldContainer}>
<View style={style.codeFieldContainer}>
<CodeField
{...props}
testID={testID}
accessibilityLabel={accessibilityLabel}
accessible
value={PIN}
rootStyle={PINInputTheme.codeFieldRoot}
onChangeText={onChangeText}
cellCount={minPINLength}
keyboardType="numeric"
textContentType="password"
renderCell={({ index, symbol, isFocused }) => {
let child: React.ReactNode | string = ''
if (symbol) {
child = showPIN ? symbol : '●' // Show or hide PIN
} else if (isFocused) {
child = <Cursor />
}
return (
<View key={index} style={style.cell} onLayout={getCellOnLayoutHandler(index)}>
<Text style={style.cellText} maxFontSizeMultiplier={1}>
{child}
</Text>
</View>
)
}}
autoFocus={autoFocus}
ref={ref}
/>
</View>
<TouchableOpacity
style={style.hideIcon}
accessibilityLabel={showPIN ? t('PINCreate.Hide') : t('PINCreate.Show')}
accessibilityRole={'button'}
testID={showPIN ? testIdWithKey('Hide') : testIdWithKey('Show')}
onPress={() => setShowPIN(!showPIN)}
hitSlop={hitSlop}
>
<Icon color={PINInputTheme.icon.color} name={showPIN ? 'visibility-off' : 'visibility'} size={30} />
</TouchableOpacity>
</View>
{inlineMessagePlaceholder(InlineErrorPosition.Above)}
{content()}
{inlineMessagePlaceholder(InlineErrorPosition.Below)}
</View>
)
}
Expand Down
5 changes: 4 additions & 1 deletion packages/legacy/core/App/container-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { PINExplainerProps } from './screens/PINExplainer'
import { CredentialListFooterProps } from './types/credential-list-footer'
import { ContactListItemProps } from './components/listItems/ContactListItem'
import { ContactCredentialListItemProps } from './components/listItems/ContactCredentialListItem'
import { InlineErrorConfig } from './types/error'

export type FN_ONBOARDING_DONE = (
dispatch: React.Dispatch<ReducerAction<unknown>>,
Expand All @@ -47,7 +48,7 @@ export const SCREEN_TOKENS = {
SCREEN_SPLASH: 'screen.splash',
SCREEN_SCAN: 'screen.scan',
SCREEN_USE_BIOMETRY: 'screen.use-biometry',
SCREEN_PIN_EXPLAINER: 'screen.pin-explainer'
SCREEN_PIN_EXPLAINER: 'screen.pin-explainer',
} as const

export const NAV_TOKENS = {
Expand Down Expand Up @@ -117,6 +118,7 @@ export const UTILITY_TOKENS = {

export const CONFIG_TOKENS = {
CONFIG: 'config',
INLINE_ERRORS: 'errors.inline',
} as const

export const TOKENS = {
Expand Down Expand Up @@ -186,6 +188,7 @@ export type TokenMapping = {
[TOKENS.COMPONENT_RECORD]: React.FC
[TOKENS.COMPONENT_CONTACT_LIST_ITEM]: React.FC<ContactListItemProps>
[TOKENS.COMPONENT_CONTACT_DETAILS_CRED_LIST_ITEM]: React.FC<ContactCredentialListItemProps>
[TOKENS.INLINE_ERRORS]: InlineErrorConfig
[TOKENS.CUSTOM_NAV_STACK_1]: React.FC
}

Expand Down
2 changes: 2 additions & 0 deletions packages/legacy/core/App/container-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { Config } from './types/config'
import { Locales } from './localization'
import ContactListItem from './components/listItems/ContactListItem'
import ContactCredentialListItem from './components/listItems/ContactCredentialListItem'
import { InlineErrorPosition } from './types/error'

export const defaultConfig: Config = {
PINSecurity: { rules: PINRules, displayHelper: false },
Expand Down Expand Up @@ -126,6 +127,7 @@ export class MainContainer implements Container {
this._container.registerInstance(TOKENS.COMPONENT_CONTACT_DETAILS_CRED_LIST_ITEM, ContactCredentialListItem)
this._container.registerInstance(TOKENS.CACHE_CRED_DEFS, [])
this._container.registerInstance(TOKENS.CACHE_SCHEMAS, [])
this._container.registerInstance(TOKENS.INLINE_ERRORS, { enabled: true, position: InlineErrorPosition.Above })
this._container.registerInstance(
TOKENS.FN_ONBOARDING_DONE,
(dispatch: React.Dispatch<ReducerAction<unknown>>, navigation: StackNavigationProp<AuthenticateStackParams>) => {
Expand Down
5 changes: 5 additions & 0 deletions packages/legacy/core/App/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ export type {
Migration as MigrationState,
Tours as ToursState,
} from './types/state'

export type { InlineMessageProps } from './components/inputs/InlineErrorText'

export type { InlineErrorPosition } from './types/error'

export type { CredentialListFooterProps }
export * from './container-api'
export { MainContainer } from './container-impl'
Expand Down
Loading

0 comments on commit 31734d5

Please sign in to comment.