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

chore: display error messages for PIN screens inline mode #1253

Merged
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
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
Loading