@bifold/core 2.1.10 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/lib/commonjs/App.js +1 -2
  2. package/lib/commonjs/App.js.map +1 -1
  3. package/lib/commonjs/assets/oca-bundles.json +4 -4
  4. package/lib/commonjs/components/buttons/Button.js +26 -10
  5. package/lib/commonjs/components/buttons/Button.js.map +1 -1
  6. package/lib/commonjs/components/inputs/PINInput.js +4 -2
  7. package/lib/commonjs/components/inputs/PINInput.js.map +1 -1
  8. package/lib/commonjs/components/misc/CredentialCard11.js +3 -3
  9. package/lib/commonjs/components/misc/CredentialCard11.js.map +1 -1
  10. package/lib/commonjs/components/modals/DeveloperModal.js +2 -8
  11. package/lib/commonjs/components/modals/DeveloperModal.js.map +1 -1
  12. package/lib/commonjs/components/views/Banner.js +162 -0
  13. package/lib/commonjs/components/views/Banner.js.map +1 -0
  14. package/lib/commonjs/components/views/HeaderWithBanner.js +17 -0
  15. package/lib/commonjs/components/views/HeaderWithBanner.js.map +1 -0
  16. package/lib/commonjs/components/views/KeyboardView.js +21 -8
  17. package/lib/commonjs/components/views/KeyboardView.js.map +1 -1
  18. package/lib/commonjs/constants.js.map +1 -1
  19. package/lib/commonjs/container-api.js +1 -0
  20. package/lib/commonjs/container-api.js.map +1 -1
  21. package/lib/commonjs/container-impl.js +2 -0
  22. package/lib/commonjs/container-impl.js.map +1 -1
  23. package/lib/commonjs/contexts/network.js +36 -0
  24. package/lib/commonjs/contexts/network.js.map +1 -1
  25. package/lib/commonjs/contexts/reducers/store.js +78 -0
  26. package/lib/commonjs/contexts/reducers/store.js.map +1 -1
  27. package/lib/commonjs/contexts/store.js +6 -1
  28. package/lib/commonjs/contexts/store.js.map +1 -1
  29. package/lib/commonjs/hooks/useBifoldAgentSetup.js +5 -5
  30. package/lib/commonjs/hooks/useBifoldAgentSetup.js.map +1 -1
  31. package/lib/commonjs/index.js +0 -8
  32. package/lib/commonjs/index.js.map +1 -1
  33. package/lib/commonjs/localization/en/en.json +10 -1
  34. package/lib/commonjs/localization/fr/fr.json +10 -1
  35. package/lib/commonjs/localization/pt-br/pt-br.json +10 -1
  36. package/lib/commonjs/modules/openid/screens/OpenIDProofChangeCredential.js +7 -4
  37. package/lib/commonjs/modules/openid/screens/OpenIDProofChangeCredential.js.map +1 -1
  38. package/lib/commonjs/navigators/DeliveryStack.js +15 -3
  39. package/lib/commonjs/navigators/DeliveryStack.js.map +1 -1
  40. package/lib/commonjs/navigators/MainStack.js.map +1 -1
  41. package/lib/commonjs/navigators/SettingStack.js +9 -0
  42. package/lib/commonjs/navigators/SettingStack.js.map +1 -1
  43. package/lib/commonjs/navigators/defaultStackOptions.js +2 -0
  44. package/lib/commonjs/navigators/defaultStackOptions.js.map +1 -1
  45. package/lib/commonjs/screens/Chat.js +11 -1
  46. package/lib/commonjs/screens/Chat.js.map +1 -1
  47. package/lib/commonjs/screens/ConfigureMediator.js +156 -0
  48. package/lib/commonjs/screens/ConfigureMediator.js.map +1 -0
  49. package/lib/commonjs/screens/PINChange.js +9 -4
  50. package/lib/commonjs/screens/PINChange.js.map +1 -1
  51. package/lib/commonjs/screens/PINEnter.js +8 -2
  52. package/lib/commonjs/screens/PINEnter.js.map +1 -1
  53. package/lib/commonjs/screens/PINVerify.js +13 -11
  54. package/lib/commonjs/screens/PINVerify.js.map +1 -1
  55. package/lib/commonjs/screens/Settings.js +6 -0
  56. package/lib/commonjs/screens/Settings.js.map +1 -1
  57. package/lib/commonjs/screens/ToggleBiometry.js +2 -1
  58. package/lib/commonjs/screens/ToggleBiometry.js.map +1 -1
  59. package/lib/commonjs/theme.js +55 -0
  60. package/lib/commonjs/theme.js.map +1 -1
  61. package/lib/commonjs/types/navigators.js +1 -0
  62. package/lib/commonjs/types/navigators.js.map +1 -1
  63. package/lib/commonjs/types/state.js +4 -0
  64. package/lib/commonjs/utils/helpers.js +10 -0
  65. package/lib/commonjs/utils/helpers.js.map +1 -1
  66. package/lib/commonjs/utils/mediatorhelpers.js +63 -0
  67. package/lib/commonjs/utils/mediatorhelpers.js.map +1 -0
  68. package/lib/module/App.js +1 -2
  69. package/lib/module/App.js.map +1 -1
  70. package/lib/module/assets/oca-bundles.json +4 -4
  71. package/lib/module/components/buttons/Button.js +26 -10
  72. package/lib/module/components/buttons/Button.js.map +1 -1
  73. package/lib/module/components/inputs/PINInput.js +4 -2
  74. package/lib/module/components/inputs/PINInput.js.map +1 -1
  75. package/lib/module/components/misc/CredentialCard11.js +3 -3
  76. package/lib/module/components/misc/CredentialCard11.js.map +1 -1
  77. package/lib/module/components/modals/DeveloperModal.js +2 -8
  78. package/lib/module/components/modals/DeveloperModal.js.map +1 -1
  79. package/lib/module/components/views/Banner.js +152 -0
  80. package/lib/module/components/views/Banner.js.map +1 -0
  81. package/lib/module/components/views/HeaderWithBanner.js +10 -0
  82. package/lib/module/components/views/HeaderWithBanner.js.map +1 -0
  83. package/lib/module/components/views/KeyboardView.js +22 -9
  84. package/lib/module/components/views/KeyboardView.js.map +1 -1
  85. package/lib/module/constants.js.map +1 -1
  86. package/lib/module/container-api.js +1 -0
  87. package/lib/module/container-api.js.map +1 -1
  88. package/lib/module/container-impl.js +2 -0
  89. package/lib/module/container-impl.js.map +1 -1
  90. package/lib/module/contexts/network.js +37 -1
  91. package/lib/module/contexts/network.js.map +1 -1
  92. package/lib/module/contexts/reducers/store.js +77 -0
  93. package/lib/module/contexts/reducers/store.js.map +1 -1
  94. package/lib/module/contexts/store.js +6 -1
  95. package/lib/module/contexts/store.js.map +1 -1
  96. package/lib/module/hooks/useBifoldAgentSetup.js +5 -5
  97. package/lib/module/hooks/useBifoldAgentSetup.js.map +1 -1
  98. package/lib/module/index.js +1 -2
  99. package/lib/module/index.js.map +1 -1
  100. package/lib/module/localization/en/en.json +10 -1
  101. package/lib/module/localization/fr/fr.json +10 -1
  102. package/lib/module/localization/pt-br/pt-br.json +10 -1
  103. package/lib/module/modules/openid/screens/OpenIDProofChangeCredential.js +7 -4
  104. package/lib/module/modules/openid/screens/OpenIDProofChangeCredential.js.map +1 -1
  105. package/lib/module/navigators/DeliveryStack.js +14 -3
  106. package/lib/module/navigators/DeliveryStack.js.map +1 -1
  107. package/lib/module/navigators/MainStack.js.map +1 -1
  108. package/lib/module/navigators/SettingStack.js +9 -0
  109. package/lib/module/navigators/SettingStack.js.map +1 -1
  110. package/lib/module/navigators/defaultStackOptions.js +2 -0
  111. package/lib/module/navigators/defaultStackOptions.js.map +1 -1
  112. package/lib/module/screens/Chat.js +11 -1
  113. package/lib/module/screens/Chat.js.map +1 -1
  114. package/lib/module/screens/ConfigureMediator.js +149 -0
  115. package/lib/module/screens/ConfigureMediator.js.map +1 -0
  116. package/lib/module/screens/PINChange.js +9 -4
  117. package/lib/module/screens/PINChange.js.map +1 -1
  118. package/lib/module/screens/PINEnter.js +8 -2
  119. package/lib/module/screens/PINEnter.js.map +1 -1
  120. package/lib/module/screens/PINVerify.js +13 -11
  121. package/lib/module/screens/PINVerify.js.map +1 -1
  122. package/lib/module/screens/Settings.js +6 -0
  123. package/lib/module/screens/Settings.js.map +1 -1
  124. package/lib/module/screens/ToggleBiometry.js +2 -1
  125. package/lib/module/screens/ToggleBiometry.js.map +1 -1
  126. package/lib/module/theme.js +55 -0
  127. package/lib/module/theme.js.map +1 -1
  128. package/lib/module/types/navigators.js +1 -0
  129. package/lib/module/types/navigators.js.map +1 -1
  130. package/lib/module/types/state.js +1 -1
  131. package/lib/module/utils/helpers.js +10 -0
  132. package/lib/module/utils/helpers.js.map +1 -1
  133. package/lib/module/utils/mediatorhelpers.js +55 -0
  134. package/lib/module/utils/mediatorhelpers.js.map +1 -0
  135. package/lib/typescript/src/App.d.ts.map +1 -1
  136. package/lib/typescript/src/components/buttons/Button.d.ts.map +1 -1
  137. package/lib/typescript/src/components/inputs/PINInput.d.ts +1 -0
  138. package/lib/typescript/src/components/inputs/PINInput.d.ts.map +1 -1
  139. package/lib/typescript/src/components/modals/DeveloperModal.d.ts.map +1 -1
  140. package/lib/typescript/src/components/views/Banner.d.ts +16 -0
  141. package/lib/typescript/src/components/views/Banner.d.ts.map +1 -0
  142. package/lib/typescript/src/components/views/HeaderWithBanner.d.ts +5 -0
  143. package/lib/typescript/src/components/views/HeaderWithBanner.d.ts.map +1 -0
  144. package/lib/typescript/src/components/views/KeyboardView.d.ts +1 -0
  145. package/lib/typescript/src/components/views/KeyboardView.d.ts.map +1 -1
  146. package/lib/typescript/src/constants.d.ts.map +1 -1
  147. package/lib/typescript/src/container-api.d.ts +3 -0
  148. package/lib/typescript/src/container-api.d.ts.map +1 -1
  149. package/lib/typescript/src/container-impl.d.ts +1 -0
  150. package/lib/typescript/src/container-impl.d.ts.map +1 -1
  151. package/lib/typescript/src/contexts/network.d.ts.map +1 -1
  152. package/lib/typescript/src/contexts/reducers/store.d.ts +11 -1
  153. package/lib/typescript/src/contexts/reducers/store.d.ts.map +1 -1
  154. package/lib/typescript/src/contexts/store.d.ts.map +1 -1
  155. package/lib/typescript/src/hooks/useBifoldAgentSetup.d.ts.map +1 -1
  156. package/lib/typescript/src/index.d.ts +1 -2
  157. package/lib/typescript/src/index.d.ts.map +1 -1
  158. package/lib/typescript/src/modules/openid/screens/OpenIDProofChangeCredential.d.ts.map +1 -1
  159. package/lib/typescript/src/navigators/DeliveryStack.d.ts.map +1 -1
  160. package/lib/typescript/src/navigators/MainStack.d.ts.map +1 -1
  161. package/lib/typescript/src/navigators/SettingStack.d.ts.map +1 -1
  162. package/lib/typescript/src/navigators/defaultStackOptions.d.ts.map +1 -1
  163. package/lib/typescript/src/screens/Chat.d.ts.map +1 -1
  164. package/lib/typescript/src/screens/ConfigureMediator.d.ts +6 -0
  165. package/lib/typescript/src/screens/ConfigureMediator.d.ts.map +1 -0
  166. package/lib/typescript/src/screens/PINChange.d.ts.map +1 -1
  167. package/lib/typescript/src/screens/PINEnter.d.ts.map +1 -1
  168. package/lib/typescript/src/screens/PINVerify.d.ts.map +1 -1
  169. package/lib/typescript/src/screens/Settings.d.ts.map +1 -1
  170. package/lib/typescript/src/screens/ToggleBiometry.d.ts.map +1 -1
  171. package/lib/typescript/src/theme.d.ts +82 -0
  172. package/lib/typescript/src/theme.d.ts.map +1 -1
  173. package/lib/typescript/src/types/navigators.d.ts +5 -1
  174. package/lib/typescript/src/types/navigators.d.ts.map +1 -1
  175. package/lib/typescript/src/types/state.d.ts +4 -0
  176. package/lib/typescript/src/types/state.d.ts.map +1 -1
  177. package/lib/typescript/src/utils/helpers.d.ts.map +1 -1
  178. package/lib/typescript/src/utils/mediatorhelpers.d.ts +4 -0
  179. package/lib/typescript/src/utils/mediatorhelpers.d.ts.map +1 -0
  180. package/package.json +3 -3
  181. package/src/App.tsx +0 -2
  182. package/src/assets/oca-bundles.json +4 -4
  183. package/src/components/buttons/Button.tsx +50 -20
  184. package/src/components/inputs/PINInput.tsx +3 -1
  185. package/src/components/misc/CredentialCard11.tsx +5 -5
  186. package/src/components/modals/DeveloperModal.tsx +1 -2
  187. package/src/components/views/Banner.tsx +174 -0
  188. package/src/components/views/HeaderWithBanner.tsx +17 -0
  189. package/src/components/views/KeyboardView.tsx +23 -9
  190. package/src/constants.ts +0 -1
  191. package/src/container-api.ts +2 -0
  192. package/src/container-impl.ts +2 -0
  193. package/src/contexts/network.tsx +39 -1
  194. package/src/contexts/reducers/store.ts +76 -0
  195. package/src/contexts/store.tsx +4 -0
  196. package/src/hooks/useBifoldAgentSetup.ts +5 -5
  197. package/src/index.ts +0 -2
  198. package/src/localization/en/en.json +10 -1
  199. package/src/localization/fr/fr.json +10 -1
  200. package/src/localization/pt-br/pt-br.json +10 -1
  201. package/src/modules/openid/screens/OpenIDProofChangeCredential.tsx +5 -4
  202. package/src/navigators/DeliveryStack.tsx +14 -3
  203. package/src/navigators/MainStack.tsx +4 -1
  204. package/src/navigators/SettingStack.tsx +10 -0
  205. package/src/navigators/defaultStackOptions.tsx +2 -0
  206. package/src/screens/Chat.tsx +25 -19
  207. package/src/screens/ConfigureMediator.tsx +160 -0
  208. package/src/screens/PINChange.tsx +4 -1
  209. package/src/screens/PINEnter.tsx +5 -1
  210. package/src/screens/PINVerify.tsx +34 -32
  211. package/src/screens/Settings.tsx +7 -0
  212. package/src/screens/ToggleBiometry.tsx +1 -0
  213. package/src/theme.ts +55 -0
  214. package/src/types/navigators.ts +2 -0
  215. package/src/types/state.ts +5 -0
  216. package/src/utils/helpers.ts +9 -0
  217. package/src/utils/mediatorhelpers.ts +60 -0
  218. package/lib/commonjs/components/network/NetInfo.js +0 -43
  219. package/lib/commonjs/components/network/NetInfo.js.map +0 -1
  220. package/lib/module/components/network/NetInfo.js +0 -36
  221. package/lib/module/components/network/NetInfo.js.map +0 -1
  222. package/lib/typescript/src/components/network/NetInfo.d.ts +0 -3
  223. package/lib/typescript/src/components/network/NetInfo.d.ts.map +0 -1
  224. package/src/components/network/NetInfo.tsx +0 -36
@@ -0,0 +1,160 @@
1
+ import { FlatList, StyleSheet, View, Pressable } from 'react-native'
2
+ import BouncyCheckbox from 'react-native-bouncy-checkbox'
3
+ import { SafeAreaView } from 'react-native-safe-area-context'
4
+ import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
5
+ import { useStore } from '../contexts/store'
6
+ import { DispatchAction } from '../contexts/reducers/store'
7
+ import { useTheme } from '../contexts/theme'
8
+ import { ThemedText } from '../components/texts/ThemedText'
9
+ import { testIdWithKey } from '../utils/testable'
10
+ import { useEffect, useState } from 'react'
11
+ import { LockoutReason, useAuth } from '../contexts/auth'
12
+ import { useAgent } from '@credo-ts/react-hooks'
13
+ import { MediationRecipientService } from '@credo-ts/core'
14
+ import DismissiblePopupModal from '../components/modals/DismissiblePopupModal'
15
+ import { useTranslation } from 'react-i18next'
16
+ import { StackScreenProps } from '@react-navigation/stack'
17
+ import { Screens, SettingStackParams } from '../types/navigators'
18
+ import { setMediationToDefault } from '../utils/mediatorhelpers'
19
+
20
+ type MediatorItem = {
21
+ id: string
22
+ label: string
23
+ testID: string
24
+ }
25
+ type ConfigureMediatorProps = StackScreenProps<SettingStackParams, Screens.ConfigureMediator>
26
+
27
+ const ConfigureMediator = ({ route }: ConfigureMediatorProps) => {
28
+ const [store, dispatch] = useStore()
29
+ const { agent } = useAgent()
30
+ const { ColorPallet, SettingsTheme } = useTheme()
31
+ const { t } = useTranslation()
32
+ const { lockOutUser } = useAuth()
33
+ const supportedMediators = store.preferences.availableMediators
34
+ const scannedMediatorUri = route.params?.scannedMediatorUri
35
+ const [isModalVisible, setIsModalVisible] = useState(false)
36
+ const [pendingMediatorId, setPendingMediatorId] = useState<string | null>(null)
37
+
38
+ useEffect(() => {
39
+ if (scannedMediatorUri && !store.preferences.availableMediators.includes(scannedMediatorUri)) {
40
+ dispatch({
41
+ type: DispatchAction.ADD_AVAILABLE_MEDIATOR,
42
+ payload: [scannedMediatorUri],
43
+ })
44
+ }
45
+ }, [scannedMediatorUri, dispatch, store.preferences.availableMediators])
46
+ const mediators: MediatorItem[] = supportedMediators.map((mediator) => ({
47
+ id: mediator,
48
+ label: String(mediator),
49
+ testID: testIdWithKey(mediator),
50
+ }))
51
+ const styles = StyleSheet.create({
52
+ container: {
53
+ backgroundColor: ColorPallet.brand.primaryBackground,
54
+ width: '100%',
55
+ },
56
+ section: {
57
+ backgroundColor: SettingsTheme.groupBackground,
58
+ paddingHorizontal: 25,
59
+ paddingVertical: 16,
60
+ },
61
+ sectionRow: {
62
+ flexDirection: 'row',
63
+ alignItems: 'center',
64
+ justifyContent: 'space-between',
65
+ },
66
+ itemSeparator: {
67
+ borderBottomWidth: 1,
68
+ borderBottomColor: ColorPallet.brand.primaryBackground,
69
+ marginHorizontal: 25,
70
+ },
71
+ checkboxContainer: {
72
+ justifyContent: 'center',
73
+ },
74
+ })
75
+
76
+ const confirmMediatorChange = async () => {
77
+ if (!pendingMediatorId || !agent) return
78
+
79
+ await agent.dependencyManager.resolve(MediationRecipientService).clearDefaultMediator(agent.context)
80
+ agent.config.logger.info(`successfully cleared default mediator`)
81
+ await setMediationToDefault(agent, pendingMediatorId)
82
+ dispatch({
83
+ type: DispatchAction.SET_SELECTED_MEDIATOR,
84
+ payload: [pendingMediatorId],
85
+ })
86
+ lockOutUser(LockoutReason.Logout)
87
+ setIsModalVisible(false)
88
+ }
89
+
90
+ const handleMediatorChange = async (mediatorId: string) => {
91
+ if (mediatorId === store.preferences.selectedMediator) return
92
+ setPendingMediatorId(mediatorId)
93
+ setIsModalVisible(true)
94
+ }
95
+
96
+ const MediatorRow = ({
97
+ label,
98
+ id,
99
+ testID,
100
+ selected,
101
+ onPress,
102
+ }: {
103
+ label: string
104
+ id: string
105
+ testID: string
106
+ selected: boolean
107
+ onPress: (id: string) => void
108
+ }) => (
109
+ <View style={[styles.section, styles.sectionRow]}>
110
+ <ThemedText variant="title">{label}</ThemedText>
111
+ <Pressable
112
+ style={styles.checkboxContainer}
113
+ accessibilityLabel={label}
114
+ accessibilityRole="radio"
115
+ testID={testIdWithKey(testID)}
116
+ >
117
+ <BouncyCheckbox
118
+ disableText
119
+ fillColor={ColorPallet.brand.secondaryBackground}
120
+ unfillColor={ColorPallet.brand.secondaryBackground}
121
+ size={36}
122
+ innerIconStyle={{ borderColor: ColorPallet.brand.primary, borderWidth: 2 }}
123
+ ImageComponent={() => <Icon name="circle" size={18} color={ColorPallet.brand.primary} />}
124
+ onPress={() => onPress(id)}
125
+ isChecked={selected}
126
+ disableBuiltInState
127
+ />
128
+ </Pressable>
129
+ </View>
130
+ )
131
+
132
+ return (
133
+ <SafeAreaView style={styles.container}>
134
+ <FlatList
135
+ data={mediators}
136
+ keyExtractor={(item) => item.id}
137
+ renderItem={({ item }) => (
138
+ <MediatorRow
139
+ label={item.label.split('?')[0]}
140
+ id={item.id}
141
+ testID={item.testID}
142
+ selected={store.preferences.selectedMediator === item.id}
143
+ onPress={handleMediatorChange}
144
+ />
145
+ )}
146
+ />
147
+ {isModalVisible && (
148
+ <DismissiblePopupModal
149
+ title={t('Settings.ChangeMediator')}
150
+ description={t('Settings.ChangeMediatorDescription')}
151
+ onCallToActionLabel={t('Global.Confirm')}
152
+ onCallToActionPressed={() => confirmMediatorChange()}
153
+ onDismissPressed={() => setIsModalVisible(false)}
154
+ />
155
+ )}
156
+ </SafeAreaView>
157
+ )
158
+ }
159
+
160
+ export default ConfigureMediator
@@ -174,7 +174,7 @@ const PINChange: React.FC<StackScreenProps<ParamListBase, Screens.ChangePIN>> =
174
174
  }, [inlineMessages, isLoading, PIN, PINTwo, PINOld])
175
175
 
176
176
  return (
177
- <KeyboardView>
177
+ <KeyboardView keyboardAvoiding={false}>
178
178
  <View style={style.screenContainer}>
179
179
  <View style={style.contentContainer}>
180
180
  <PINHeader updatePin />
@@ -185,6 +185,7 @@ const PINChange: React.FC<StackScreenProps<ParamListBase, Screens.ChangePIN>> =
185
185
  onPINChanged={(p: string) => {
186
186
  setPINOld(p)
187
187
  }}
188
+ onSubmitEditing={handleChangePinTap}
188
189
  />
189
190
  <PINInput
190
191
  label={t('PINChange.EnterPINTitle')}
@@ -202,6 +203,7 @@ const PINChange: React.FC<StackScreenProps<ParamListBase, Screens.ChangePIN>> =
202
203
  accessibilityLabel={t('PINCreate.EnterPIN')}
203
204
  autoFocus={false}
204
205
  inlineMessage={inlineMessageField1}
206
+ onSubmitEditing={handleChangePinTap}
205
207
  />
206
208
  <PINInput
207
209
  label={t('PINChange.ReenterPIN')}
@@ -220,6 +222,7 @@ const PINChange: React.FC<StackScreenProps<ParamListBase, Screens.ChangePIN>> =
220
222
  autoFocus={false}
221
223
  ref={PINTwoInputRef}
222
224
  inlineMessage={inlineMessageField2}
225
+ onSubmitEditing={handleChangePinTap}
223
226
  />
224
227
  {PINSecurity.displayHelper && <PINValidationHelper validations={PINValidations} />}
225
228
  {modalState.visible && (
@@ -316,7 +316,7 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated }) => {
316
316
  ])
317
317
 
318
318
  return (
319
- <KeyboardView>
319
+ <KeyboardView keyboardAvoiding={false}>
320
320
  <View style={style.screenContainer}>
321
321
  <View>
322
322
  <Pressable
@@ -333,12 +333,16 @@ const PINEnter: React.FC<PINEnterProps> = ({ setAuthenticated }) => {
333
333
  setPIN(p)
334
334
  if (p.length === minPINLength) {
335
335
  Keyboard.dismiss()
336
+ onPINInputCompleted(p)
336
337
  }
337
338
  }}
338
339
  testID={testIdWithKey('EnterPIN')}
339
340
  accessibilityLabel={t('PINEnter.EnterPIN')}
340
341
  autoFocus={true}
341
342
  inlineMessage={inlineMessageField}
343
+ onSubmitEditing={() => {
344
+ onPINInputCompleted(PIN)
345
+ }}
342
346
  />
343
347
  <ThemedText
344
348
  variant="bold"
@@ -37,8 +37,6 @@ const PINVerify: React.FC<Props> = ({ setAuthenticated, usage = PINEntryUsage.PI
37
37
  const { ColorPallet } = useTheme()
38
38
  const { ButtonLoading } = useAnimatedComponents()
39
39
  const [inlineMessageField, setInlineMessageField] = useState<InlineMessageProps>()
40
- // Temporary until all use cases are built with the new design
41
- const isNewDesign = usage === PINEntryUsage.ChangeBiometrics
42
40
  const [{ preventScreenCapture }] = useServices([TOKENS.CONFIG])
43
41
  usePreventScreenCapture(preventScreenCapture)
44
42
 
@@ -97,61 +95,65 @@ const PINVerify: React.FC<Props> = ({ setAuthenticated, usage = PINEntryUsage.PI
97
95
  flex: 1,
98
96
  padding: 20,
99
97
  backgroundColor: ColorPallet.brand.primaryBackground,
100
- justifyContent: isNewDesign ? 'flex-start' : 'space-between',
98
+ justifyContent: 'space-between',
101
99
  },
102
100
  buttonContainer: {
103
- marginTop: 'auto',
104
101
  width: '100%',
105
102
  },
106
103
  helpText: {
107
104
  alignSelf: 'auto',
108
105
  textAlign: 'left',
109
- marginBottom: isNewDesign ? 40 : 20,
106
+ marginBottom: 40,
110
107
  },
111
108
  inputLabelText: {
112
109
  alignSelf: 'auto',
113
110
  textAlign: 'left',
114
- marginBottom: isNewDesign ? 20 : 4,
111
+ marginBottom: 20,
115
112
  },
116
113
  modalText: {
117
114
  marginVertical: 5,
118
115
  },
119
116
  changeBiometricsHeader: {
120
117
  marginTop: 0,
121
- marginBottom: isNewDesign ? 40 : 20,
118
+ marginBottom: 40,
122
119
  },
123
120
  })
124
121
 
125
122
  return (
126
- <KeyboardView>
123
+ <KeyboardView keyboardAvoiding={false}>
127
124
  <View style={style.screenContainer}>
128
- {usage === PINEntryUsage.ChangeBiometrics && (
129
- <ThemedText variant="headingTwo" style={style.changeBiometricsHeader}>
130
- {t('PINEnter.ChangeBiometricsHeader')}
131
- </ThemedText>
132
- )}
133
- <ThemedText style={style.helpText}>{helpText[usage]}</ThemedText>
134
- <ThemedText variant="bold" style={style.inputLabelText}>
135
- {inputLabelText[usage]}
125
+ <View>
136
126
  {usage === PINEntryUsage.ChangeBiometrics && (
137
- <ThemedText variant="caption">
138
- {` `}
139
- {t('PINEnter.ChangeBiometricsInputLabelParenthesis')}
127
+ <ThemedText variant="headingTwo" style={style.changeBiometricsHeader}>
128
+ {t('PINEnter.ChangeBiometricsHeader')}
140
129
  </ThemedText>
141
130
  )}
142
- </ThemedText>
143
- <PINInput
144
- onPINChanged={(p: string) => {
145
- setPIN(p)
146
- if (p.length === minPINLength) {
147
- Keyboard.dismiss()
148
- }
149
- }}
150
- testID={testIdWithKey(inputTestId[usage])}
151
- accessibilityLabel={inputLabelText[usage]}
152
- autoFocus={true}
153
- inlineMessage={inlineMessageField}
154
- />
131
+ <ThemedText style={style.helpText}>{helpText[usage]}</ThemedText>
132
+ <ThemedText variant="bold" style={style.inputLabelText}>
133
+ {inputLabelText[usage]}
134
+ {usage === PINEntryUsage.ChangeBiometrics && (
135
+ <ThemedText variant="caption">
136
+ {` `}
137
+ {t('PINEnter.ChangeBiometricsInputLabelParenthesis')}
138
+ </ThemedText>
139
+ )}
140
+ </ThemedText>
141
+ <PINInput
142
+ onPINChanged={(p: string) => {
143
+ setPIN(p)
144
+ if (p.length === minPINLength) {
145
+ Keyboard.dismiss()
146
+ }
147
+ }}
148
+ testID={testIdWithKey(inputTestId[usage])}
149
+ accessibilityLabel={inputLabelText[usage]}
150
+ autoFocus={true}
151
+ inlineMessage={inlineMessageField}
152
+ onSubmitEditing={async () => {
153
+ await onPINInputCompleted()
154
+ }}
155
+ />
156
+ </View>
155
157
  <View style={style.buttonContainer}>
156
158
  <Button
157
159
  title={primaryButtonText[usage]}
@@ -218,6 +218,13 @@ const Settings: React.FC<SettingsProps> = ({ navigation }) => {
218
218
  testID: testIdWithKey('DeveloperOptions'),
219
219
  onPress: () => navigation.navigate(Screens.Developer),
220
220
  },
221
+ {
222
+ title: t('Settings.ConfigureMediator'),
223
+ value: store.preferences.selectedMediator,
224
+ accessibilityLabel: t('Settings.ConfigureMediator'),
225
+ testID: testIdWithKey('ConfigureMediator'),
226
+ onPress: () => navigation.navigate(Screens.ConfigureMediator),
227
+ },
221
228
  {
222
229
  title: t('Settings.Logout'),
223
230
  accessibilityLabel: t('Settings.Logout'),
@@ -136,6 +136,7 @@ const ToggleBiometry: React.FC = () => {
136
136
  transparent={false}
137
137
  animationType={'slide'}
138
138
  presentationStyle={'fullScreen'}
139
+ statusBarTranslucent={true}
139
140
  >
140
141
  <SafeAreaView edges={['top']} style={{ backgroundColor: NavigationTheme.colors.primary }} />
141
142
  <FauxHeader title={t('Screens.EnterPIN')} onBackPressed={onBackPressed} />
package/src/theme.ts CHANGED
@@ -521,6 +521,16 @@ export const Buttons = StyleSheet.create({
521
521
  borderRadius: 4,
522
522
  backgroundColor: ColorPallet.brand.primary,
523
523
  },
524
+ criticalText: {
525
+ ...TextTheme.bold,
526
+ color: ColorPallet.brand.buttonText,
527
+ textAlign: 'center',
528
+ },
529
+ criticalTextDisabled: {
530
+ ...TextTheme.bold,
531
+ color: ColorPallet.brand.buttonText,
532
+ textAlign: 'center',
533
+ },
524
534
  primary: {
525
535
  padding: 16,
526
536
  borderRadius: 4,
@@ -583,13 +593,39 @@ export const Buttons = StyleSheet.create({
583
593
  borderRadius: 4,
584
594
  backgroundColor: ColorPallet.brand.primary,
585
595
  },
596
+ modalCriticalDisabled: {
597
+ padding: 16,
598
+ borderRadius: 4,
599
+ backgroundColor: ColorPallet.brand.primaryDisabled,
600
+ },
601
+ modalCriticalText: {
602
+ ...TextTheme.bold,
603
+ color: ColorPallet.brand.buttonText,
604
+ textAlign: 'center',
605
+ },
606
+ modalCriticalTextDisabled: {
607
+ ...TextTheme.bold,
608
+ color: ColorPallet.brand.buttonText,
609
+ textAlign: 'center',
610
+ },
586
611
  modalPrimary: {
587
612
  padding: 16,
588
613
  borderRadius: 4,
589
614
  backgroundColor: ColorPallet.brand.modalPrimary,
590
615
  },
616
+ modalPrimaryDisabled: {
617
+ padding: 16,
618
+ borderRadius: 4,
619
+ backgroundColor: ColorPallet.brand.primaryDisabled,
620
+ },
591
621
  modalPrimaryText: {
592
622
  ...TextTheme.bold,
623
+ color: ColorPallet.brand.buttonText,
624
+ textAlign: 'center',
625
+ },
626
+ modalPrimaryTextDisabled: {
627
+ ...TextTheme.bold,
628
+ color: ColorPallet.brand.buttonText,
593
629
  textAlign: 'center',
594
630
  },
595
631
  modalSecondary: {
@@ -598,19 +634,38 @@ export const Buttons = StyleSheet.create({
598
634
  borderWidth: 2,
599
635
  borderColor: ColorPallet.brand.modalPrimary,
600
636
  },
637
+ modalSecondaryDisabled: {
638
+ padding: 16,
639
+ borderRadius: 4,
640
+ borderWidth: 2,
641
+ borderColor: ColorPallet.brand.secondaryDisabled,
642
+ },
601
643
  modalSecondaryText: {
602
644
  ...TextTheme.bold,
603
645
  color: ColorPallet.brand.modalPrimary,
604
646
  textAlign: 'center',
605
647
  },
648
+ modalSecondaryTextDisabled: {
649
+ ...TextTheme.bold,
650
+ color: ColorPallet.brand.secondaryDisabled,
651
+ textAlign: 'center',
652
+ },
606
653
  modalTertiary: {
607
654
  padding: 16,
608
655
  },
656
+ modalTertiaryDisabled: {
657
+ padding: 16,
658
+ },
609
659
  modalTertiaryText: {
610
660
  ...TextTheme.bold,
611
661
  color: ColorPallet.brand.modalPrimary,
612
662
  textAlign: 'center',
613
663
  },
664
+ modalTertiaryTextDisabled: {
665
+ ...TextTheme.bold,
666
+ color: ColorPallet.brand.tertiaryDisabled,
667
+ textAlign: 'center',
668
+ },
614
669
  })
615
670
 
616
671
  export const ListItems = StyleSheet.create({
@@ -58,6 +58,7 @@ export enum Screens {
58
58
  HistoryDetails = 'History details',
59
59
  AutoLock = 'AutoLock',
60
60
  UpdateAvailable = 'Update Available',
61
+ ConfigureMediator = 'Configure Mediator',
61
62
  }
62
63
 
63
64
  export enum Stacks {
@@ -171,6 +172,7 @@ export type SettingStackParams = {
171
172
  [Screens.TogglePushNotifications]: undefined
172
173
  [Screens.HistorySettings]: undefined
173
174
  [Screens.AutoLock]: undefined
175
+ [Screens.ConfigureMediator]: { scannedMediatorUri: string } | undefined
174
176
  }
175
177
 
176
178
  export type NotificationStackParams = {
@@ -1,3 +1,5 @@
1
+ import { BannerMessage } from 'components/views/Banner'
2
+
1
3
  export interface Onboarding {
2
4
  didSeePreface: boolean
3
5
  didCompleteTutorial: boolean
@@ -33,6 +35,9 @@ export interface Preferences {
33
35
  alternateContactNames: Record<string, string>
34
36
  autoLockTime: number
35
37
  theme?: string
38
+ selectedMediator: string
39
+ availableMediators: string[]
40
+ bannerMessages: BannerMessage[]
36
41
  }
37
42
 
38
43
  export interface Tours {
@@ -57,6 +57,7 @@ import {
57
57
  } from './anonCredsProofRequestMapper'
58
58
  import { parseCredDefFromId } from './cred-def'
59
59
  import { isOpenIdCredentialOffer, isOpenIdPresentationRequest } from './parsers'
60
+ import { isMediatorInvitation } from './mediatorhelpers'
60
61
 
61
62
  export { parsedCredDefNameFromCredential } from './cred-def'
62
63
 
@@ -1091,6 +1092,14 @@ export const connectFromScanOrDeepLink = async (
1091
1092
 
1092
1093
  return
1093
1094
  }
1095
+ if (await isMediatorInvitation(agent, uri)) {
1096
+ navigation.navigate(Stacks.SettingStack as any, {
1097
+ screen: Screens.ConfigureMediator,
1098
+ params: { scannedMediatorUri: uri },
1099
+ })
1100
+
1101
+ return
1102
+ }
1094
1103
 
1095
1104
  const aUrl = processBetaUrlIfRequired(uri)
1096
1105
  const receivedInvitation = await connectFromInvitation(aUrl, agent, implicitInvitations, reuseConnection)
@@ -0,0 +1,60 @@
1
+ import { Agent, MediationRecord } from '@credo-ts/core'
2
+
3
+ export const isMediatorInvitation = async (agent: Agent, url: string): Promise<boolean> => {
4
+ const invitation = await agent.oob.parseInvitation(url)
5
+ if (!invitation) {
6
+ return false
7
+ }
8
+ if (invitation.goalCode === 'aries.vc.mediate') {
9
+ agent.config.logger.info(`Invitation is a mediator invitation with goal code: ${invitation.goalCode}`)
10
+ return true
11
+ }
12
+ agent.config.logger.info(`Invitation is not a mediator invitation, goal code: ${invitation.goalCode}`)
13
+ return false
14
+ }
15
+
16
+ const provisionMediationRecordFromMediatorUrl = async (
17
+ agent: Agent,
18
+ url: string
19
+ ): Promise<MediationRecord | undefined> => {
20
+ try {
21
+ const invitation = await agent.oob.parseInvitation(url)
22
+ if (!invitation) {
23
+ agent.config.logger.warn(`No invitation found in URL: ${url}`)
24
+ return undefined
25
+ }
26
+ const outOfBandRecord = await agent.oob.findByReceivedInvitationId(invitation.id)
27
+ let [connection] = outOfBandRecord ? await agent.connections.findAllByOutOfBandId(outOfBandRecord.id) : []
28
+
29
+ if (!connection) {
30
+ agent.config.logger.warn(`No connection found for out-of-band record: ${outOfBandRecord?.id}`)
31
+ const invite = await agent.oob.parseInvitation(url)
32
+ const { connectionRecord: newConnection } = await agent.oob.receiveInvitation(invite)
33
+ if (!newConnection) {
34
+ agent.config.logger.warn(`Failed to create connection from invitation: ${JSON.stringify(invite, null, 2)}`)
35
+ return
36
+ }
37
+ connection = newConnection
38
+ }
39
+ const result = connection.isReady ? connection : await agent.connections.returnWhenIsConnected(connection.id)
40
+ return agent.mediationRecipient.provision(result)
41
+ } catch (error) {
42
+ agent.config.logger.warn(`Failed to get connection ID from mediator URL: ${error}`)
43
+ return
44
+ }
45
+ }
46
+
47
+ export const setMediationToDefault = async (agent: Agent, mediatorUrl: string) => {
48
+ const mediationRecord = await provisionMediationRecordFromMediatorUrl(agent, mediatorUrl)
49
+ if (!mediationRecord) {
50
+ agent.config.logger.warn(`No connection record found for mediator URL: ${mediatorUrl}`)
51
+ return
52
+ }
53
+ const currentDefault = await agent.mediationRecipient.findDefaultMediator()
54
+ if (currentDefault?.connectionId === mediationRecord.id) {
55
+ agent.config.logger.info(`Default mediator already set for connection ID: ${mediationRecord.id}`)
56
+ return
57
+ }
58
+ await agent.mediationRecipient.setDefaultMediator(mediationRecord)
59
+ agent.config.logger.info(`setting default mediator with record: ${JSON.stringify(mediationRecord)}`)
60
+ }
@@ -1,43 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _react = require("react");
8
- var _reactI18next = require("react-i18next");
9
- var _reactNativeToastMessage = _interopRequireDefault(require("react-native-toast-message"));
10
- var _network = require("../../contexts/network");
11
- var _BaseToast = require("../toast/BaseToast");
12
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
- const NetInfo = () => {
14
- const {
15
- assertInternetReachable
16
- } = (0, _network.useNetwork)();
17
- const {
18
- t
19
- } = (0, _reactI18next.useTranslation)();
20
- const [hasShown, setHasShown] = (0, _react.useState)(false);
21
- const showNetworkWarning = (0, _react.useCallback)(() => {
22
- setHasShown(true);
23
- _reactNativeToastMessage.default.show({
24
- type: _BaseToast.ToastType.Error,
25
- autoHide: true,
26
- text1: t('NetInfo.NoInternetConnectionTitle')
27
- });
28
- }, [t]);
29
- (0, _react.useEffect)(() => {
30
- const internetReachable = assertInternetReachable();
31
- if (internetReachable) {
32
- _reactNativeToastMessage.default.hide();
33
- }
34
-
35
- // Strict check for false, null means the network state is not yet known
36
- if (internetReachable === false && !hasShown) {
37
- showNetworkWarning();
38
- }
39
- }, [showNetworkWarning, assertInternetReachable, hasShown]);
40
- return null;
41
- };
42
- var _default = exports.default = NetInfo;
43
- //# sourceMappingURL=NetInfo.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_react","require","_reactI18next","_reactNativeToastMessage","_interopRequireDefault","_network","_BaseToast","e","__esModule","default","NetInfo","assertInternetReachable","useNetwork","t","useTranslation","hasShown","setHasShown","useState","showNetworkWarning","useCallback","Toast","show","type","ToastType","Error","autoHide","text1","useEffect","internetReachable","hide","_default","exports"],"sourceRoot":"../../../../src","sources":["components/network/NetInfo.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,aAAA,GAAAD,OAAA;AACA,IAAAE,wBAAA,GAAAC,sBAAA,CAAAH,OAAA;AACA,IAAAI,QAAA,GAAAJ,OAAA;AACA,IAAAK,UAAA,GAAAL,OAAA;AAA8C,SAAAG,uBAAAG,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAE9C,MAAMG,OAAiB,GAAGA,CAAA,KAAM;EAC9B,MAAM;IAAEC;EAAwB,CAAC,GAAG,IAAAC,mBAAU,EAAC,CAAC;EAChD,MAAM;IAAEC;EAAE,CAAC,GAAG,IAAAC,4BAAc,EAAC,CAAC;EAC9B,MAAM,CAACC,QAAQ,EAAEC,WAAW,CAAC,GAAG,IAAAC,eAAQ,EAAC,KAAK,CAAC;EAE/C,MAAMC,kBAAkB,GAAG,IAAAC,kBAAW,EAAC,MAAM;IAC3CH,WAAW,CAAC,IAAI,CAAC;IACjBI,gCAAK,CAACC,IAAI,CAAC;MACTC,IAAI,EAAEC,oBAAS,CAACC,KAAK;MACrBC,QAAQ,EAAE,IAAI;MACdC,KAAK,EAAEb,CAAC,CAAC,mCAAmC;IAC9C,CAAC,CAAC;EACJ,CAAC,EAAE,CAACA,CAAC,CAAC,CAAC;EAEP,IAAAc,gBAAS,EAAC,MAAM;IACd,MAAMC,iBAAiB,GAAGjB,uBAAuB,CAAC,CAAC;IACnD,IAAIiB,iBAAiB,EAAE;MACrBR,gCAAK,CAACS,IAAI,CAAC,CAAC;IACd;;IAEA;IACA,IAAID,iBAAiB,KAAK,KAAK,IAAI,CAACb,QAAQ,EAAE;MAC5CG,kBAAkB,CAAC,CAAC;IACtB;EACF,CAAC,EAAE,CAACA,kBAAkB,EAAEP,uBAAuB,EAAEI,QAAQ,CAAC,CAAC;EAE3D,OAAO,IAAI;AACb,CAAC;AAAA,IAAAe,QAAA,GAAAC,OAAA,CAAAtB,OAAA,GAEcC,OAAO","ignoreList":[]}
@@ -1,36 +0,0 @@
1
- import { useEffect, useCallback, useState } from 'react';
2
- import { useTranslation } from 'react-i18next';
3
- import Toast from 'react-native-toast-message';
4
- import { useNetwork } from '../../contexts/network';
5
- import { ToastType } from '../toast/BaseToast';
6
- const NetInfo = () => {
7
- const {
8
- assertInternetReachable
9
- } = useNetwork();
10
- const {
11
- t
12
- } = useTranslation();
13
- const [hasShown, setHasShown] = useState(false);
14
- const showNetworkWarning = useCallback(() => {
15
- setHasShown(true);
16
- Toast.show({
17
- type: ToastType.Error,
18
- autoHide: true,
19
- text1: t('NetInfo.NoInternetConnectionTitle')
20
- });
21
- }, [t]);
22
- useEffect(() => {
23
- const internetReachable = assertInternetReachable();
24
- if (internetReachable) {
25
- Toast.hide();
26
- }
27
-
28
- // Strict check for false, null means the network state is not yet known
29
- if (internetReachable === false && !hasShown) {
30
- showNetworkWarning();
31
- }
32
- }, [showNetworkWarning, assertInternetReachable, hasShown]);
33
- return null;
34
- };
35
- export default NetInfo;
36
- //# sourceMappingURL=NetInfo.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["useEffect","useCallback","useState","useTranslation","Toast","useNetwork","ToastType","NetInfo","assertInternetReachable","t","hasShown","setHasShown","showNetworkWarning","show","type","Error","autoHide","text1","internetReachable","hide"],"sourceRoot":"../../../../src","sources":["components/network/NetInfo.tsx"],"mappings":"AAAA,SAASA,SAAS,EAAEC,WAAW,EAAEC,QAAQ,QAAQ,OAAO;AACxD,SAASC,cAAc,QAAQ,eAAe;AAC9C,OAAOC,KAAK,MAAM,4BAA4B;AAC9C,SAASC,UAAU,QAAQ,wBAAwB;AACnD,SAASC,SAAS,QAAQ,oBAAoB;AAE9C,MAAMC,OAAiB,GAAGA,CAAA,KAAM;EAC9B,MAAM;IAAEC;EAAwB,CAAC,GAAGH,UAAU,CAAC,CAAC;EAChD,MAAM;IAAEI;EAAE,CAAC,GAAGN,cAAc,CAAC,CAAC;EAC9B,MAAM,CAACO,QAAQ,EAAEC,WAAW,CAAC,GAAGT,QAAQ,CAAC,KAAK,CAAC;EAE/C,MAAMU,kBAAkB,GAAGX,WAAW,CAAC,MAAM;IAC3CU,WAAW,CAAC,IAAI,CAAC;IACjBP,KAAK,CAACS,IAAI,CAAC;MACTC,IAAI,EAAER,SAAS,CAACS,KAAK;MACrBC,QAAQ,EAAE,IAAI;MACdC,KAAK,EAAER,CAAC,CAAC,mCAAmC;IAC9C,CAAC,CAAC;EACJ,CAAC,EAAE,CAACA,CAAC,CAAC,CAAC;EAEPT,SAAS,CAAC,MAAM;IACd,MAAMkB,iBAAiB,GAAGV,uBAAuB,CAAC,CAAC;IACnD,IAAIU,iBAAiB,EAAE;MACrBd,KAAK,CAACe,IAAI,CAAC,CAAC;IACd;;IAEA;IACA,IAAID,iBAAiB,KAAK,KAAK,IAAI,CAACR,QAAQ,EAAE;MAC5CE,kBAAkB,CAAC,CAAC;IACtB;EACF,CAAC,EAAE,CAACA,kBAAkB,EAAEJ,uBAAuB,EAAEE,QAAQ,CAAC,CAAC;EAE3D,OAAO,IAAI;AACb,CAAC;AAED,eAAeH,OAAO","ignoreList":[]}
@@ -1,3 +0,0 @@
1
- declare const NetInfo: React.FC;
2
- export default NetInfo;
3
- //# sourceMappingURL=NetInfo.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"NetInfo.d.ts","sourceRoot":"","sources":["../../../../../src/components/network/NetInfo.tsx"],"names":[],"mappings":"AAMA,QAAA,MAAM,OAAO,EAAE,KAAK,CAAC,EA2BpB,CAAA;AAED,eAAe,OAAO,CAAA"}